diff --git a/include/ignition/gazebo/EntityComponentManager.hh b/include/ignition/gazebo/EntityComponentManager.hh index 87fdb22c4e..d078dbf3a0 100644 --- a/include/ignition/gazebo/EntityComponentManager.hh +++ b/include/ignition/gazebo/EntityComponentManager.hh @@ -738,7 +738,7 @@ namespace ignition /// \tparam ComponentTypeTs All the component types that define a view. /// \return A pointer to the view. private: template - detail::View *FindView() const; + detail::View *FindView() const; /// \brief Find a view based on the provided component type ids. /// \param[in] _types The component type ids that serve as a key into diff --git a/include/ignition/gazebo/detail/BaseView.hh b/include/ignition/gazebo/detail/BaseView.hh index eea6755316..9c1761d604 100644 --- a/include/ignition/gazebo/detail/BaseView.hh +++ b/include/ignition/gazebo/detail/BaseView.hh @@ -68,7 +68,7 @@ struct ComponentTypeHasher class IGNITION_GAZEBO_VISIBLE BaseView { /// \brief Destructor - public: virtual ~BaseView() = default; + public: virtual ~BaseView(); /// \brief See if an entity is a part of the view /// \param[in] _entity The entity diff --git a/include/ignition/gazebo/detail/EntityComponentManager.hh b/include/ignition/gazebo/detail/EntityComponentManager.hh index b92beb81c8..199be4bb7a 100644 --- a/include/ignition/gazebo/detail/EntityComponentManager.hh +++ b/include/ignition/gazebo/detail/EntityComponentManager.hh @@ -372,6 +372,50 @@ void EntityComponentManager::EachNoCache(typename identity +constexpr bool applyFunctionImpl(const FuncT &_f, const Entity &_entity, + const std::vector &_data, + std::index_sequence) +{ + return _f(_entity, static_cast(_data[Is])...); +} + +/// \brief Helper template to call a callback function with each of the +/// components in the _data vector expanded as arguments to the callback +/// function. +/// \tparam ComponentTypeTs The actual types of each of the components. +/// \tparam FuncT The type of the callback function. +/// \tparam BaseComponentT Either "BaseComponent" or "const BaseComponent" +/// \param[in] _f The callback function +/// \param[in] _entity The entity associated with the components. +/// \param[in] _data A vector of component pointers that will be expanded to +/// become the arguments of the callback function _f. +/// \return The value of return by the function _f. +template +constexpr bool applyFunction(const FuncT &_f, const Entity &_entity, + const std::vector &_data) +{ + return applyFunctionImpl( + _f, _entity, _data, std::index_sequence_for{}); +} +} // namespace detail + ////////////////////////////////////////////////// template void EntityComponentManager::Each(typename identityEntities()) { - if (!std::apply(_f, view->EntityComponentConstData(entity))) + const auto &data = view->EntityComponentData(entity); + if (!detail::applyFunction(_f, entity, data)) { break; } @@ -405,7 +450,8 @@ void EntityComponentManager::Each(typename identityEntities()) { - if (!std::apply(_f, view->EntityComponentData(entity))) + const auto &data = view->EntityComponentData(entity); + if (!detail::applyFunction(_f, entity, data)) { break; } @@ -434,7 +480,8 @@ void EntityComponentManager::EachNew(typename identityNewEntities()) { - if (!std::apply(_f, view->EntityComponentData(entity))) + const auto &data = view->EntityComponentData(entity); + if (!detail::applyFunction(_f, entity, data)) { break; } @@ -455,7 +502,8 @@ void EntityComponentManager::EachNew(typename identityNewEntities()) { - if (!std::apply(_f, view->EntityComponentConstData(entity))) + const auto &data = view->EntityComponentData(entity); + if (!detail::applyFunction(_f, entity, data)) { break; } @@ -476,7 +524,8 @@ void EntityComponentManager::EachRemoved(typename identityToRemoveEntities()) { - if (!std::apply(_f, view->EntityComponentConstData(entity))) + const auto &data = view->EntityComponentData(entity); + if (!detail::applyFunction(_f, entity, data)) { break; } @@ -485,7 +534,7 @@ void EntityComponentManager::EachRemoved(typename identity -detail::View *EntityComponentManager::FindView() const +detail::View *EntityComponentManager::FindView() const { auto viewKey = std::vector{ComponentTypeTs::typeId...}; @@ -493,7 +542,7 @@ detail::View *EntityComponentManager::FindView() const auto baseViewPtr = baseViewMutexPair.first; if (nullptr != baseViewPtr) { - auto view = static_cast*>(baseViewPtr); + auto view = static_cast(baseViewPtr); std::unique_ptr> viewLock; if (this->LockAddingEntitiesToViews()) @@ -527,7 +576,7 @@ detail::View *EntityComponentManager::FindView() const } // create a new view if one wasn't found - detail::View view; + detail::View view(std::set{ComponentTypeTs::typeId...}); for (const auto &vertex : this->Entities().Vertices()) { @@ -547,8 +596,8 @@ detail::View *EntityComponentManager::FindView() const } baseViewPtr = this->AddView(viewKey, - std::make_unique>(view)); - return static_cast*>(baseViewPtr); + std::make_unique(std::move(view))); + return static_cast(baseViewPtr); } ////////////////////////////////////////////////// diff --git a/include/ignition/gazebo/detail/View.hh b/include/ignition/gazebo/detail/View.hh index 5589fbc02f..7a214cf1fb 100644 --- a/include/ignition/gazebo/detail/View.hh +++ b/include/ignition/gazebo/detail/View.hh @@ -17,13 +17,16 @@ #ifndef IGNITION_GAZEBO_DETAIL_VIEW_HH_ #define IGNITION_GAZEBO_DETAIL_VIEW_HH_ +#include #include #include #include #include +#include #include +#include "ignition/gazebo/components/Component.hh" #include "ignition/gazebo/Entity.hh" #include "ignition/gazebo/config.hh" #include "ignition/gazebo/detail/BaseView.hh" @@ -37,19 +40,23 @@ inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { namespace detail { /// \brief A view that caches a particular set of component type data. -/// \tparam ComponentTypeTs The component type(s) that are stored in this view. -template -class View : public BaseView +/// +/// Note that symbols for this class are visible because methods from this class +/// are used in templated Ignition::Gazebo::EntityComponentManager methods. +/// However, users should not use this class (or anything else in namespace +/// ignition::gazebo::detail) directly. +class IGNITION_GAZEBO_VISIBLE View : public BaseView { /// \brief Alias for containers that hold and entity and its component data. /// The component types held in this container match the component types that /// were specified when creating the view. - private: using ComponentData = std::tuple; + private: using ComponentData = std::vector; private: using ConstComponentData = - std::tuple; + std::vector; /// \brief Constructor - public: View(); + /// \param[in] _compIds a set of IDs of the components cached by this View. + public: explicit View(const std::set &_compIds); /// \brief Documentation inherited public: bool HasCachedComponentData(const Entity _entity) const override; @@ -62,7 +69,7 @@ class View : public BaseView /// \param[_in] _entity The entity /// \return The entity and its component data. Const pointers to the component /// data are returned. - public: ConstComponentData EntityComponentConstData( + public: const ConstComponentData &EntityComponentConstData( const Entity _entity) const; /// \brief Get an entity and its component data. It is assumed that the entity @@ -70,28 +77,36 @@ class View : public BaseView /// \param[_in] _entity The entity /// \return The entity and its component data. Mutable pointers to the /// component data are returned. - public: ComponentData EntityComponentData(const Entity _entity); + public: const ComponentData &EntityComponentData(const Entity _entity) const; /// \brief Add an entity with its component data to the view. It is assumed /// that the entity to be added does not already exist in the view. + /// \tparam ComponentTypeTs The component type(s) that are stored in this + /// view. These types correspond to each of the types in the _compPtrs + /// parameter of this function. /// \param[in] _entity The entity /// \param[in] _new Whether to add the entity to the list of new entities. /// The new here is to indicate whether the entity is new to the entity /// component manager. An existing entity can be added when creating a new /// view or when rebuilding the view. /// \param[in] _compPtrs Const pointers to the entity's components - public: void AddEntityWithConstComps(const Entity &_entity, const bool _new, + public: template + void AddEntityWithConstComps(const Entity &_entity, const bool _new, const ComponentTypeTs*... _compPtrs); /// \brief Add an entity with its component data to the view. It is assumed /// that the entity to be added does not already exist in the view. + /// \tparam ComponentTypeTs The component type(s) that are stored in this + /// view. These types correspond to each of the types in the _compPtrs + /// parameter of this function. /// \param[in] _entity The entity /// \param[in] _new Whether to add the entity to the list of new entities. /// The new here is to indicate whether the entity is new to the entity /// component manager. An existing entity can be added when creating a new /// view or when rebuilding the view. /// \param[in] _compPtrs Pointers to the entity's components - public: void AddEntityWithComps(const Entity &_entity, const bool _new, + public: template + void AddEntityWithComps(const Entity &_entity, const bool _new, ComponentTypeTs*... _compPtrs); /// \brief Documentation inherited @@ -146,189 +161,28 @@ class View : public BaseView }; ////////////////////////////////////////////////// -template -View::View() +template +void View::AddEntityWithConstComps(const Entity &_entity, const bool _new, + const ComponentTypeTs *... _compPtrs) { - this->componentTypes = {ComponentTypeTs::typeId...}; -} - -////////////////////////////////////////////////// -template -bool View::HasCachedComponentData( - const Entity _entity) const -{ - auto cachedComps = - this->validData.find(_entity) != this->validData.end() || - this->invalidData.find(_entity) != this->invalidData.end(); - auto cachedConstComps = - this->validConstData.find(_entity) != this->validConstData.end() || - this->invalidConstData.find(_entity) != this->invalidConstData.end(); - - if (cachedComps && !cachedConstComps) - { - ignwarn << "Non-const component data is cached for entity " << _entity - << ", but const component data is not cached." << std::endl; - } - else if (cachedConstComps && !cachedComps) - { - ignwarn << "Const component data is cached for entity " << _entity - << ", but non-const component data is not cached." << std::endl; - } - - return cachedComps && cachedConstComps; -} - -////////////////////////////////////////////////// -template -bool View::RemoveEntity(const Entity _entity) -{ - this->invalidData.erase(_entity); - this->invalidConstData.erase(_entity); - this->missingCompTracker.erase(_entity); - - if (!this->HasEntity(_entity) && !this->IsEntityMarkedForAddition(_entity)) - return false; - - this->entities.erase(_entity); - this->newEntities.erase(_entity); - this->toRemoveEntities.erase(_entity); - this->toAddEntities.erase(_entity); - this->validData.erase(_entity); - this->validConstData.erase(_entity); - - return true; -} - -////////////////////////////////////////////////// -template -typename View::ConstComponentData - View::EntityComponentConstData(const Entity _entity) const -{ - return this->validConstData.at(_entity); -} - -////////////////////////////////////////////////// -template -typename View::ComponentData - View::EntityComponentData(const Entity _entity) -{ - return this->validData.at(_entity); -} - -////////////////////////////////////////////////// -template -void View::AddEntityWithConstComps(const Entity &_entity, - const bool _new, const ComponentTypeTs*... _compPtrs) -{ - this->validConstData[_entity] = std::make_tuple(_entity, _compPtrs...); + this->validConstData[_entity] = + std::vector{_compPtrs...}; this->entities.insert(_entity); if (_new) this->newEntities.insert(_entity); } ////////////////////////////////////////////////// -template -void View::AddEntityWithComps(const Entity &_entity, - const bool _new, ComponentTypeTs*... _compPtrs) +template +void View::AddEntityWithComps(const Entity &_entity, const bool _new, + ComponentTypeTs *... _compPtrs) { - this->validData[_entity] = std::make_tuple(_entity, _compPtrs...); + this->validData[_entity] = + std::vector{_compPtrs...}; this->entities.insert(_entity); if (_new) this->newEntities.insert(_entity); } - -////////////////////////////////////////////////// -template -bool View::NotifyComponentAddition(const Entity _entity, - bool _newEntity, const ComponentTypeId _typeId) -{ - // make sure that _typeId is a type required by the view and that _entity is - // already a part of the view - if (!this->RequiresComponent(_typeId) || - !this->HasCachedComponentData(_entity)) - return false; - - // remove the newly added component type from the missing component types - // list - auto missingCompsIter = this->missingCompTracker.find(_entity); - if (missingCompsIter == this->missingCompTracker.end()) - { - // the component is already added, so nothing else needs to be done - return true; - } - missingCompsIter->second.erase(_typeId); - - // if the entity now has all components that meet the requirements of the - // view, then add the entity back to the view - if (missingCompsIter->second.empty()) - { - auto nh = this->invalidData.extract(_entity); - this->validData.insert(std::move(nh)); - auto constCompNh = this->invalidConstData.extract(_entity); - this->validConstData.insert(std::move(constCompNh)); - this->entities.insert(_entity); - if (_newEntity) - this->newEntities.insert(_entity); - this->missingCompTracker.erase(_entity); - } - - return true; -} - -////////////////////////////////////////////////// -template -bool View::NotifyComponentRemoval(const Entity _entity, - const ComponentTypeId _typeId) -{ - // if entity is still marked as to add, remove from the view - if (this->RequiresComponent(_typeId)) - this->toAddEntities.erase(_entity); - - // make sure that _typeId is a type required by the view and that _entity is - // already a part of the view - if (!this->RequiresComponent(_typeId) || - !this->HasCachedComponentData(_entity)) - return false; - - // if the component being removed is the first component that causes _entity - // to be invalid for this view, move _entity from validData to invalidData - // since _entity should no longer be considered a part of the view - auto it = this->validData.find(_entity); - auto constCompIt = this->validConstData.find(_entity); - if (it != this->validData.end() && - constCompIt != this->validConstData.end()) - { - auto nh = this->validData.extract(it); - this->invalidData.insert(std::move(nh)); - auto constCompNh = this->validConstData.extract(constCompIt); - this->invalidConstData.insert(std::move(constCompNh)); - this->entities.erase(_entity); - this->newEntities.erase(_entity); - } - - this->missingCompTracker[_entity].insert(_typeId); - - return true; -} - -////////////////////////////////////////////////// -template -void View::Reset() -{ - // reset all data structures in the BaseView except for componentTypes since - // the view always requires the types in componentTypes - this->entities.clear(); - this->newEntities.clear(); - this->toRemoveEntities.clear(); - this->toAddEntities.clear(); - - // reset all data structures unique to the templated view - this->validData.clear(); - this->validConstData.clear(); - this->invalidData.clear(); - this->invalidConstData.clear(); - this->missingCompTracker.clear(); -} } // namespace detail } // namespace IGNITION_GAZEBO_VERSION_NAMESPACE } // namespace gazebo diff --git a/src/BaseView.cc b/src/BaseView.cc index 23e78ee607..276b2ac175 100644 --- a/src/BaseView.cc +++ b/src/BaseView.cc @@ -23,6 +23,9 @@ using namespace ignition; using namespace gazebo; using namespace detail; +////////////////////////////////////////////////// +BaseView::~BaseView() = default; + ////////////////////////////////////////////////// bool BaseView::HasEntity(const Entity _entity) const { diff --git a/src/BaseView_TEST.cc b/src/BaseView_TEST.cc index 855168266d..4fa3db60c7 100644 --- a/src/BaseView_TEST.cc +++ b/src/BaseView_TEST.cc @@ -39,7 +39,8 @@ class BaseViewTest : public InternalFixture<::testing::Test> ///////////////////////////////////////////////// TEST_F(BaseViewTest, ComponentTypes) { - auto modelNameView = detail::View(); + auto modelNameView = + detail::View({components::Model::typeId, components::Name::typeId}); // make sure that a view's required component types are initialized properly EXPECT_EQ(2u, modelNameView.ComponentTypes().size()); @@ -55,7 +56,8 @@ TEST_F(BaseViewTest, ComponentTypes) ///////////////////////////////////////////////// TEST_F(BaseViewTest, ToAddEntities) { - auto modelNameView = detail::View(); + auto modelNameView = + detail::View({components::Model::typeId, components::Name::typeId}); const Entity e1 = 1; auto e1IsNew = true; @@ -114,7 +116,8 @@ TEST_F(BaseViewTest, ToAddEntities) ///////////////////////////////////////////////// TEST_F(BaseViewTest, AddEntities) { - auto modelNameView = detail::View(); + auto modelNameView = + detail::View({components::Model::typeId, components::Name::typeId}); // Initially, the view should have no entities EXPECT_EQ(0u, modelNameView.Entities().size()); @@ -152,30 +155,30 @@ TEST_F(BaseViewTest, AddEntities) modelNameView.NewEntities().end()); auto e1ConstData = modelNameView.EntityComponentConstData(e1); - EXPECT_EQ(e1, std::get(e1ConstData)); - EXPECT_EQ(&e1ModelComp, std::get(e1ConstData)); - EXPECT_EQ(&e1NameComp, std::get(e1ConstData)); + ASSERT_EQ(2u, e1ConstData.size()); + EXPECT_EQ(&e1ModelComp, e1ConstData[0]); + EXPECT_EQ(&e1NameComp, e1ConstData[1]); auto e1Data = modelNameView.EntityComponentData(e1); - EXPECT_EQ(e1, std::get(e1Data)); - EXPECT_EQ(&e1ModelComp, std::get(e1Data)); - EXPECT_EQ(&e1NameComp, std::get(e1Data)); + ASSERT_EQ(2u, e1Data.size()); + EXPECT_EQ(&e1ModelComp, e1Data[0]); + EXPECT_EQ(&e1NameComp, e1Data[1]); - auto e2ConstData = modelNameView.EntityComponentConstData(e2); - EXPECT_EQ(e2, std::get(e2ConstData)); - EXPECT_EQ(&e2ModelComp, std::get(e2ConstData)); - EXPECT_EQ(&e2NameComp, std::get(e2ConstData)); + auto e2ConstData = modelNameView .EntityComponentConstData(e2); + ASSERT_EQ(2u, e2ConstData.size()); + EXPECT_EQ(&e2ModelComp, e2ConstData[0]); + EXPECT_EQ(&e2NameComp, e2ConstData[1]); auto e2Data = modelNameView.EntityComponentData(e2); - EXPECT_EQ(e2, std::get(e2Data)); - EXPECT_EQ(&e2ModelComp, std::get(e2Data)); - EXPECT_EQ(&e2NameComp, std::get(e2Data)); + ASSERT_EQ(2u, e2Data.size()); + EXPECT_EQ(&e2ModelComp, e2Data[0]); + EXPECT_EQ(&e2NameComp, e2Data[1]); } ///////////////////////////////////////////////// TEST_F(BaseViewTest, RemoveEntities) { - auto view = detail::View(); + auto view = detail::View({components::Model::typeId}); const Entity e1 = 1; auto e1ModelComp = components::Model(); @@ -225,7 +228,7 @@ TEST_F(BaseViewTest, RemoveEntities) ///////////////////////////////////////////////// TEST_F(BaseViewTest, Reset) { - auto view = detail::View(); + auto view = detail::View({components::Model::typeId}); // initially, the view should be completely empty, except for its component // types (the view's component types are defined at object instantiation time @@ -304,7 +307,7 @@ TEST_F(BaseViewTest, Reset) ///////////////////////////////////////////////// TEST_F(BaseViewTest, CachedComponentData) { - auto view = detail::View(); + auto view = detail::View({components::Model::typeId}); const Entity e1 = 1; const auto e1IsNew = true; @@ -333,7 +336,8 @@ TEST_F(BaseViewTest, CachedComponentData) ///////////////////////////////////////////////// TEST_F(BaseViewTest, ComponentChangeNotification) { - auto view = detail::View(); + auto view = detail::View({components::Model::typeId, + components::Visual::typeId}); const Entity e1 = 1; const auto e1IsNew = true; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c609bec040..764d1e0bd6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -63,6 +63,7 @@ set (sources SystemManager.cc TestFixture.cc Util.cc + View.cc World.cc cmd/ModelCommandAPI.cc ${PROTO_PRIVATE_SRC} diff --git a/src/View.cc b/src/View.cc new file mode 100644 index 0000000000..1b3fcc8df5 --- /dev/null +++ b/src/View.cc @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "ignition/gazebo/detail/View.hh" + +namespace ignition +{ +namespace gazebo +{ +// Inline bracket to help doxygen filtering. +inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { +namespace detail +{ +////////////////////////////////////////////////// +View::View(const std::set& _compIds) +{ + this->componentTypes = _compIds; +} + +////////////////////////////////////////////////// +const std::vector + &View::EntityComponentConstData(const Entity _entity) const +{ + return this->validConstData.at(_entity); +} + +////////////////////////////////////////////////// +const std::vector &View::EntityComponentData( + const Entity _entity) const +{ + return this->validData.at(_entity); +} + +////////////////////////////////////////////////// +bool View::HasCachedComponentData(const Entity _entity) const +{ + auto cachedComps = + this->validData.find(_entity) != this->validData.end() || + this->invalidData.find(_entity) != this->invalidData.end(); + auto cachedConstComps = + this->validConstData.find(_entity) != this->validConstData.end() || + this->invalidConstData.find(_entity) != this->invalidConstData.end(); + + if (cachedComps && !cachedConstComps) + { + ignwarn << "Non-const component data is cached for entity " << _entity + << ", but const component data is not cached." << std::endl; + } + else if (cachedConstComps && !cachedComps) + { + ignwarn << "Const component data is cached for entity " << _entity + << ", but non-const component data is not cached." << std::endl; + } + + return cachedComps && cachedConstComps; +} + +////////////////////////////////////////////////// +bool View::RemoveEntity(const Entity _entity) +{ + this->invalidData.erase(_entity); + this->invalidConstData.erase(_entity); + this->missingCompTracker.erase(_entity); + + if (!this->HasEntity(_entity) && !this->IsEntityMarkedForAddition(_entity)) + return false; + + this->entities.erase(_entity); + this->newEntities.erase(_entity); + this->toRemoveEntities.erase(_entity); + this->toAddEntities.erase(_entity); + this->validData.erase(_entity); + this->validConstData.erase(_entity); + + return true; +} + +////////////////////////////////////////////////// +bool View::NotifyComponentAddition(const Entity _entity, + bool _newEntity, const ComponentTypeId _typeId) +{ + // make sure that _typeId is a type required by the view and that _entity is + // already a part of the view + if (!this->RequiresComponent(_typeId) || + !this->HasCachedComponentData(_entity)) + return false; + + // remove the newly added component type from the missing component types + // list + auto missingCompsIter = this->missingCompTracker.find(_entity); + if (missingCompsIter == this->missingCompTracker.end()) + { + // the component is already added, so nothing else needs to be done + return true; + } + missingCompsIter->second.erase(_typeId); + + // if the entity now has all components that meet the requirements of the + // view, then add the entity back to the view + if (missingCompsIter->second.empty()) + { + auto nh = this->invalidData.extract(_entity); + this->validData.insert(std::move(nh)); + auto constCompNh = this->invalidConstData.extract(_entity); + this->validConstData.insert(std::move(constCompNh)); + this->entities.insert(_entity); + if (_newEntity) + this->newEntities.insert(_entity); + this->missingCompTracker.erase(_entity); + } + + return true; +} + +////////////////////////////////////////////////// +bool View::NotifyComponentRemoval(const Entity _entity, + const ComponentTypeId _typeId) +{ + // if entity is still marked as to add, remove from the view + if (this->RequiresComponent(_typeId)) + this->toAddEntities.erase(_entity); + + // make sure that _typeId is a type required by the view and that _entity is + // already a part of the view + if (!this->RequiresComponent(_typeId) || + !this->HasCachedComponentData(_entity)) + return false; + + // if the component being removed is the first component that causes _entity + // to be invalid for this view, move _entity from validData to invalidData + // since _entity should no longer be considered a part of the view + auto it = this->validData.find(_entity); + auto constCompIt = this->validConstData.find(_entity); + if (it != this->validData.end() && + constCompIt != this->validConstData.end()) + { + auto nh = this->validData.extract(it); + this->invalidData.insert(std::move(nh)); + auto constCompNh = this->validConstData.extract(constCompIt); + this->invalidConstData.insert(std::move(constCompNh)); + this->entities.erase(_entity); + this->newEntities.erase(_entity); + } + + this->missingCompTracker[_entity].insert(_typeId); + + return true; +} + +////////////////////////////////////////////////// +void View::Reset() +{ + // reset all data structures in the BaseView except for componentTypes since + // the view always requires the types in componentTypes + this->entities.clear(); + this->newEntities.clear(); + this->toRemoveEntities.clear(); + this->toAddEntities.clear(); + + // reset all data structures unique to the templated view + this->validData.clear(); + this->validConstData.clear(); + this->invalidData.clear(); + this->invalidConstData.clear(); + this->missingCompTracker.clear(); +} + +} // namespace detail +} // namespace IGNITION_GAZEBO_VERSION_NAMESPACE +} // namespace gazebo +} // namespace ignition diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index ae0de7d3da..0577088d96 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -10,6 +10,7 @@ set (gui_sources set (gtest_sources Gui_TEST.cc GuiEvents_TEST.cc + Gui_clean_exit_TEST.cc ) add_subdirectory(plugins) diff --git a/src/gui/Gui_clean_exit_TEST.cc b/src/gui/Gui_clean_exit_TEST.cc new file mode 100644 index 0000000000..b7f6e98320 --- /dev/null +++ b/src/gui/Gui_clean_exit_TEST.cc @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include +#include +#include +#include +#include + +#include "helpers/EnvTestFixture.hh" +#include "ignition/gazebo/Server.hh" +#include "ignition/gazebo/gui/Gui.hh" +#include "ignition/gazebo/test_config.hh" // NOLINT(build/include) + +using namespace ignition; + +///////////////////////////////////////////////// +class GazeboDeathTest + : public InternalFixture<::testing::TestWithParam> +{ +}; + +///////////////////////////////////////////////// +/// \brief Start the server. +/// \param[in] _fileName Full path to the SDFormat file to load. +void startServer(const std::string &_fileName) +{ + gazebo::ServerConfig config; + config.SetSdfFile(_fileName); + + gazebo::Server server(config); + EXPECT_TRUE(server.Run(true, 1, true)); +} + +///////////////////////////////////////////////// +/// \brief Start the GUI. +void startGui() +{ + int argc = 1; + char *argv = const_cast("ign-gazebo-gui"); + EXPECT_EQ(0, gazebo::gui::runGui(argc, &argv, "", "")); +} + +///////////////////////////////////////////////// +/// \brief Start both server and GUI. +/// \param[in] _fileName Full path to the SDFormat file to load. +void startBoth(const std::string &_fileName) +{ + std::thread serverThread([&]() { startServer(_fileName); }); + std::thread guiThread(startGui); + // Sleep long enough for every system to be loaded and initialized. Sending a + // SIGTERM during initialization doesn't always work properly and that is not + // what we are testing here. + using namespace std::chrono_literals; + std::this_thread::sleep_for(4s); + std::raise(SIGTERM); + serverThread.join(); + guiThread.join(); + std::exit(0); +} + +///////////////////////////////////////////////// +TEST_P(GazeboDeathTest, IGN_UTILS_TEST_ENABLED_ONLY_ON_LINUX(CleanExit)) +{ + std::string githubAction; + // This test hangs when there is high CPU usage, so we skip it on Github + // Actions. + // Note: The GITHUB_ACTIONS environment variable is automatically set when + // running on Github Actions. See https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables + if (common::env("GITHUB_ACTIONS", githubAction)) + { + GTEST_SKIP(); + } + + const auto sdfFile = + common::joinPaths(PROJECT_SOURCE_PATH, "test", "worlds", GetParam()); + ASSERT_TRUE(common::exists(sdfFile)) + << "File [" << sdfFile << "] does not exist"; + + EXPECT_EXIT(startBoth(sdfFile), testing::ExitedWithCode(0), ".*"); +} + +// Test various world files to see if any combination of systems could cause a +// crash. Note, however, that the files must not contain the Sensor system as it +// is currently not possible to run two instances of Ogre2 in one process. +INSTANTIATE_TEST_SUITE_P(WorldFiles, GazeboDeathTest, + ::testing::Values("empty.sdf", "shapes.sdf")); + +int main(int _argc, char **_argv) +{ + ::testing::InitGoogleTest(&_argc, _argv); + ::testing::FLAGS_gtest_death_test_style = "fast"; + return RUN_ALL_TESTS(); +}