From d4d824d297a8b3fde0b84f31384ab79e0e4d6c94 Mon Sep 17 00:00:00 2001 From: Louise Poubel Date: Fri, 25 Sep 2020 16:45:56 -0700 Subject: [PATCH 1/3] More world APIs, helper function ComponentData Signed-off-by: Louise Poubel --- .../ignition/gazebo/EntityComponentManager.hh | 10 + include/ignition/gazebo/World.hh | 192 +++++++++++++++ .../gazebo/detail/EntityComponentManager.hh | 12 + src/CMakeLists.txt | 2 + src/EntityComponentManager_TEST.cc | 24 ++ src/Link.cc | 40 +--- src/World.cc | 216 +++++++++++++++++ src/World_TEST.cc | 76 ++++++ test/integration/CMakeLists.txt | 1 + test/integration/world.cc | 221 ++++++++++++++++++ tutorials.md.in | 1 + tutorials/migration_world_api.md | 168 +++++++++++++ 12 files changed, 932 insertions(+), 31 deletions(-) create mode 100644 include/ignition/gazebo/World.hh create mode 100644 src/World.cc create mode 100644 src/World_TEST.cc create mode 100644 test/integration/world.cc create mode 100644 tutorials/migration_world_api.md diff --git a/include/ignition/gazebo/EntityComponentManager.hh b/include/ignition/gazebo/EntityComponentManager.hh index 37a4441f38..2bf99ad36d 100644 --- a/include/ignition/gazebo/EntityComponentManager.hh +++ b/include/ignition/gazebo/EntityComponentManager.hh @@ -214,6 +214,16 @@ namespace ignition public: template ComponentTypeT *Component(const ComponentKey &_key); + /// \brief Get the data from a component. + /// * If the component type doesn't hold any data, this won't compile. + /// * If the entity doesn't have that component, it will return nullopt. + /// * If the entity has the component, return its data. + /// \param[in] _entity The entity. + /// \return The data of the component of the specified type assigned to + /// specified Entity, or nullptr if the component could not be found. + public: template + std::optional ComponentData(const Entity _entity) const; + /// \brief Get the type IDs of all components attached to an entity. /// \param[in] _entity Entity to check. /// \return All the component type IDs. diff --git a/include/ignition/gazebo/World.hh b/include/ignition/gazebo/World.hh new file mode 100644 index 0000000000..9fdf511b36 --- /dev/null +++ b/include/ignition/gazebo/World.hh @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2020 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. + * +*/ +#ifndef IGNITION_GAZEBO_WORLD_HH_ +#define IGNITION_GAZEBO_WORLD_HH_ + +#include +#include +#include + +#include +#include + +#include "ignition/gazebo/config.hh" +#include "ignition/gazebo/EntityComponentManager.hh" +#include "ignition/gazebo/Export.hh" +#include "ignition/gazebo/Types.hh" + +namespace ignition +{ + namespace gazebo + { + // Inline bracket to help doxygen filtering. + inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { + // Forward declarations. + class IGNITION_GAZEBO_HIDDEN WorldPrivate; + // + /// \class World World.hh ignition/gazebo/World.hh + /// \brief This class provides wrappers around entities and components + /// which are more convenient and straight-forward to use than dealing + /// with the `EntityComponentManager` directly. + /// All the functions provided here are meant to be used with a world + /// entity. + /// + /// For example, given a world's entity, to find the value of its + /// name component, one could use the entity-component manager (`ecm`) + /// directly as follows: + /// + /// std::string name = ecm.Component(entity)->Data(); + /// + /// Using this class however, the same information can be obtained with + /// a simpler function call: + /// + /// World world(entity); + /// std::string name = world.Name(ecm); + class IGNITION_GAZEBO_VISIBLE World { + /// \brief Constructor + /// \param[in] _entity World entity + public: explicit World(gazebo::Entity _entity = kNullEntity); + + /// \brief Copy constructor + /// \param[in] _world World to copy. + public: World(const World &_world); + + /// \brief Move constructor + /// \param[in] _world World to move. + public: World(World &&_world) noexcept; + + /// \brief Move assignment operator. + /// \param[in] _world World component to move. + /// \return Reference to this. + public: World &operator=(World &&_world) noexcept; + + /// \brief Copy assignment operator. + /// \param[in] _world World to copy. + /// \return Reference to this. + public: World &operator=(const World &_world); + + /// \brief Destructor + public: virtual ~World(); + + /// \brief Get the entity which this World is related to. + /// \return World entity. + public: gazebo::Entity Entity() const; + + /// \brief Check whether this world correctly refers to an entity that + /// has a components::World. + /// \param[in] _ecm Entity-component manager. + /// \return True if it's a valid world in the manager. + public: bool Valid(const EntityComponentManager &_ecm) const; + + /// \brief Get the link's unscoped name. + /// \param[in] _ecm Entity-component manager. + /// \return Link's name or nullopt if the entity does not have a + /// components::Name component + public: std::optional Name( + const EntityComponentManager &_ecm) const; + + /// \brief Get the gravity in m/s^2. + /// \param[in] _ecm Entity-component manager. + /// \return Gravity vector or nullopt if the entity does not + /// have a components::Gravity component. + public: std::optional Gravity( + const EntityComponentManager &_ecm) const; + + /// \brief Get the magnetic field in T. + /// \param[in] _ecm Entity-component manager. + /// \return Magnetic field vector or nullopt if the entity does not + /// have a components::MagneticField component. + public: std::optional MagneticField( + const EntityComponentManager &_ecm) const; + + /// \brief Get atmosphere information. + /// \param[in] _ecm Entity-component manager. + /// \return Magnetic field vector or nullopt if the entity does not + /// have a components::Atmosphere component. + public: std::optional Atmosphere( + const EntityComponentManager &_ecm) const; + + /// \brief Get the ID of a light entity which is an immediate child of + /// this world. + /// \param[in] _ecm Entity-component manager. + /// \param[in] _name Light name. + /// \return Light entity. + /// \todo(anyone) Make const + public: gazebo::Entity LightByName(const EntityComponentManager &_ecm, + const std::string &_name); + + /// \brief Get the ID of a actor entity which is an immediate child of + /// this world. + /// \param[in] _ecm Entity-component manager. + /// \param[in] _name Actor name. + /// \return Actor entity. + /// \todo(anyone) Make const + public: gazebo::Entity ActorByName(const EntityComponentManager &_ecm, + const std::string &_name); + + /// \brief Get the ID of a model entity which is an immediate child of + /// this world. + /// \param[in] _ecm Entity-component manager. + /// \param[in] _name Model name. + /// \return Model entity. + /// \todo(anyone) Make const + public: gazebo::Entity ModelByName(const EntityComponentManager &_ecm, + const std::string &_name); + + /// \brief Get all lights which are immediate children of this world. + /// \param[in] _ecm Entity-component manager. + /// \return All lights in this world. + public: std::vector Lights( + const EntityComponentManager &_ecm) const; + + /// \brief Get all actors which are immediate children of this world. + /// \param[in] _ecm Entity-component manager. + /// \return All actors in this world. + public: std::vector Actors( + const EntityComponentManager &_ecm) const; + + /// \brief Get all models which are immediate children of this world. + /// \param[in] _ecm Entity-component manager. + /// \return All models in this world. + public: std::vector Models( + const EntityComponentManager &_ecm) const; + + /// \brief Get the number of lights which are immediate children of this + /// world. + /// \param[in] _ecm Entity-component manager. + /// \return Number of lights in this world. + public: uint64_t LightCount(const EntityComponentManager &_ecm) const; + + /// \brief Get the number of actors which are immediate children of this + /// world. + /// \param[in] _ecm Entity-component manager. + /// \return Number of actors in this world. + public: uint64_t ActorCount(const EntityComponentManager &_ecm) const; + + /// \brief Get the number of models which are immediate children of this + /// world. + /// \param[in] _ecm Entity-component manager. + /// \return Number of models in this world. + public: uint64_t ModelCount(const EntityComponentManager &_ecm) const; + + /// \brief Pointer to private data. + private: std::unique_ptr dataPtr; + }; + } + } +} +#endif diff --git a/include/ignition/gazebo/detail/EntityComponentManager.hh b/include/ignition/gazebo/detail/EntityComponentManager.hh index ed08397619..02f1b6cf96 100644 --- a/include/ignition/gazebo/detail/EntityComponentManager.hh +++ b/include/ignition/gazebo/detail/EntityComponentManager.hh @@ -78,6 +78,18 @@ ComponentTypeT *EntityComponentManager::Component(const ComponentKey &_key) this->ComponentImplementation(_key)); } +////////////////////////////////////////////////// +template +std::optional EntityComponentManager::ComponentData( + const Entity _entity) const +{ + auto comp = this->Component(_entity); + if (!comp) + return std::nullopt; + + return std::make_optional(comp->Data()); +} + ////////////////////////////////////////////////// template const ComponentTypeT *EntityComponentManager::First() const diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6f4259e11d..1c029e2900 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -56,6 +56,7 @@ set (sources SystemLoader.cc Util.cc View.cc + World.cc ${PROTO_PRIVATE_SRC} ${network_sources} ) @@ -78,6 +79,7 @@ set (gtest_sources System_TEST.cc SystemLoader_TEST.cc Util_TEST.cc + World_TEST.cc network/NetworkConfig_TEST.cc network/PeerTracker_TEST.cc network/NetworkManager_TEST.cc diff --git a/src/EntityComponentManager_TEST.cc b/src/EntityComponentManager_TEST.cc index e4417014a6..a8db085b1b 100644 --- a/src/EntityComponentManager_TEST.cc +++ b/src/EntityComponentManager_TEST.cc @@ -460,45 +460,69 @@ TEST_P(EntityComponentManagerFixture, ComponentValues) const auto *value = manager.Component(eInt); ASSERT_NE(nullptr, value); EXPECT_EQ(123, value->Data()); + + auto data = manager.ComponentData(eInt); + EXPECT_EQ(123, data); } { const auto *value = manager.Component(eDouble); ASSERT_NE(nullptr, value); EXPECT_DOUBLE_EQ(0.123, value->Data()); + + auto data = manager.ComponentData(eDouble); + EXPECT_EQ(0.123, data); } { const auto *value = manager.Component(eIntDouble); ASSERT_NE(nullptr, value); EXPECT_EQ(456, value->Data()); + + auto data = manager.ComponentData(eIntDouble); + EXPECT_EQ(456, data); } { const auto *value = manager.Component(eIntDouble); ASSERT_NE(nullptr, value); EXPECT_DOUBLE_EQ(0.456, value->Data()); + + auto data = manager.ComponentData(eIntDouble); + EXPECT_EQ(0.456, data); } // Failure cases { const auto *value = manager.Component(eDouble); ASSERT_EQ(nullptr, value); + + auto data = manager.ComponentData(eDouble); + EXPECT_EQ(std::nullopt, data); } { const auto *value = manager.Component(eInt); ASSERT_EQ(nullptr, value); + + auto data = manager.ComponentData(eInt); + EXPECT_EQ(std::nullopt, data); } { const auto *value = manager.Component(999); ASSERT_EQ(nullptr, value); + + auto data = manager.ComponentData(999); + EXPECT_EQ(std::nullopt, data); } { const auto *value = manager.Component(999); ASSERT_EQ(nullptr, value); + + auto data = manager.ComponentData(999); + EXPECT_EQ(std::nullopt, data); } } diff --git a/src/Link.cc b/src/Link.cc index eaed613c47..fa9190f6f9 100644 --- a/src/Link.cc +++ b/src/Link.cc @@ -90,11 +90,7 @@ bool Link::Valid(const EntityComponentManager &_ecm) const ////////////////////////////////////////////////// std::optional Link::Name(const EntityComponentManager &_ecm) const { - auto comp = _ecm.Component(this->dataPtr->id); - if (!comp) - return std::nullopt; - - return std::make_optional(comp->Data()); + return _ecm.ComponentData(this->dataPtr->id); } ////////////////////////////////////////////////// @@ -112,11 +108,8 @@ std::optional Link::ParentModel(const EntityComponentManager &_ecm) const std::optional Link::WorldPose( const EntityComponentManager &_ecm) const { - auto worldPose = _ecm.Component(this->dataPtr->id); - if (!worldPose) - return std::nullopt; - - return std::make_optional(worldPose->Data()); + return _ecm.ComponentData( + this->dataPtr->id); } ////////////////////////////////////////////////// @@ -136,13 +129,8 @@ std::optional Link::WorldInertialPose( std::optional Link::WorldLinearVelocity( const EntityComponentManager &_ecm) const { - auto worldLinVel = - _ecm.Component(this->dataPtr->id); - - if (!worldLinVel) - return std::nullopt; - - return std::make_optional(worldLinVel->Data()); + return _ecm.ComponentData( + this->dataPtr->id); } ////////////////////////////////////////////////// @@ -169,26 +157,16 @@ std::optional Link::WorldLinearVelocity( std::optional Link::WorldAngularVelocity( const EntityComponentManager &_ecm) const { - auto worldAngVel = - _ecm.Component(this->dataPtr->id); - - if (!worldAngVel) - return std::nullopt; - - return std::make_optional(worldAngVel->Data()); + return _ecm.ComponentData( + this->dataPtr->id); } ////////////////////////////////////////////////// std::optional Link::WorldLinearAcceleration( const EntityComponentManager &_ecm) const { - auto worldLinAccel = - _ecm.Component(this->dataPtr->id); - - if (!worldLinAccel) - return std::nullopt; - - return std::make_optional(worldLinAccel->Data()); + return _ecm.ComponentData(this->dataPtr->id); } ////////////////////////////////////////////////// diff --git a/src/World.cc b/src/World.cc new file mode 100644 index 0000000000..01b908a757 --- /dev/null +++ b/src/World.cc @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2020 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 "ignition/gazebo/components/Actor.hh" +#include "ignition/gazebo/components/Atmosphere.hh" +#include "ignition/gazebo/components/Gravity.hh" +#include "ignition/gazebo/components/Light.hh" +#include "ignition/gazebo/components/MagneticField.hh" +#include "ignition/gazebo/components/Model.hh" +#include "ignition/gazebo/components/Name.hh" +#include "ignition/gazebo/components/ParentEntity.hh" +#include "ignition/gazebo/components/World.hh" +#include "ignition/gazebo/World.hh" + +class ignition::gazebo::WorldPrivate +{ + /// \brief Id of world entity. + public: Entity id{kNullEntity}; +}; + +using namespace ignition; +using namespace gazebo; + +////////////////////////////////////////////////// +World::World(gazebo::Entity _entity) + : dataPtr(std::make_unique()) +{ + this->dataPtr->id = _entity; +} + +///////////////////////////////////////////////// +World::World(const World &_world) + : dataPtr(std::make_unique(*_world.dataPtr)) +{ +} + +///////////////////////////////////////////////// +World::World(World &&_world) noexcept = default; + +////////////////////////////////////////////////// +World::~World() = default; + +///////////////////////////////////////////////// +World &World::operator=(const World &_world) +{ + *this->dataPtr = (*_world.dataPtr); + return *this; +} + +///////////////////////////////////////////////// +World &World::operator=(World &&_world) noexcept = default; + +////////////////////////////////////////////////// +Entity World::Entity() const +{ + return this->dataPtr->id; +} + +////////////////////////////////////////////////// +bool World::Valid(const EntityComponentManager &_ecm) const +{ + return nullptr != _ecm.Component(this->dataPtr->id); +} + +////////////////////////////////////////////////// +std::optional World::Name(const EntityComponentManager &_ecm) const +{ + return _ecm.ComponentData(this->dataPtr->id); +} + +////////////////////////////////////////////////// +std::optional World::Atmosphere( + const EntityComponentManager &_ecm) const +{ + return _ecm.ComponentData( + this->dataPtr->id); +} + +////////////////////////////////////////////////// +std::optional World::Gravity( + const EntityComponentManager &_ecm) const +{ + return _ecm.ComponentData( + this->dataPtr->id); +} + +////////////////////////////////////////////////// +std::optional World::MagneticField( + const EntityComponentManager &_ecm) const +{ + return _ecm.ComponentData( + this->dataPtr->id); +} + +////////////////////////////////////////////////// +Entity World::LightByName(const EntityComponentManager &_ecm, + const std::string &_name) +{ + // Can't use components::Light in EntityByComponents, see + // https://github.com/ignitionrobotics/ign-gazebo/issues/376 + auto entities = _ecm.EntitiesByComponents( + components::ParentEntity(this->dataPtr->id), + components::Name(_name)); + + for (const auto &entity : entities) + { + if (_ecm.Component(entity)) + return entity; + } + return kNullEntity; +} + +////////////////////////////////////////////////// +Entity World::ActorByName(const EntityComponentManager &_ecm, + const std::string &_name) +{ + // Can't use components::Actor in EntityByComponents, see + // https://github.com/ignitionrobotics/ign-gazebo/issues/376 + auto entities = _ecm.EntitiesByComponents( + components::ParentEntity(this->dataPtr->id), + components::Name(_name)); + + for (const auto &entity : entities) + { + if (_ecm.Component(entity)) + return entity; + } + return kNullEntity; +} + +////////////////////////////////////////////////// +Entity World::ModelByName(const EntityComponentManager &_ecm, + const std::string &_name) +{ + return _ecm.EntityByComponents( + components::ParentEntity(this->dataPtr->id), + components::Name(_name), + components::Model()); +} + +////////////////////////////////////////////////// +std::vector World::Lights(const EntityComponentManager &_ecm) const +{ + // Can't use components::Light in EntityByComponents, see + // https://github.com/ignitionrobotics/ign-gazebo/issues/376 + auto entities = _ecm.EntitiesByComponents( + components::ParentEntity(this->dataPtr->id)); + + std::vector result; + for (const auto &entity : entities) + { + if (_ecm.Component(entity)) + result.push_back(entity); + } + return result; +} + +////////////////////////////////////////////////// +std::vector World::Actors(const EntityComponentManager &_ecm) const +{ + // Can't use components::Actor in EntityByComponents, see + // https://github.com/ignitionrobotics/ign-gazebo/issues/376 + auto entities = _ecm.EntitiesByComponents( + components::ParentEntity(this->dataPtr->id)); + + std::vector result; + for (const auto &entity : entities) + { + if (_ecm.Component(entity)) + result.push_back(entity); + } + return result; +} + +////////////////////////////////////////////////// +std::vector World::Models(const EntityComponentManager &_ecm) const +{ + return _ecm.EntitiesByComponents( + components::ParentEntity(this->dataPtr->id), + components::Model()); +} + +////////////////////////////////////////////////// +uint64_t World::LightCount(const EntityComponentManager &_ecm) const +{ + return this->Lights(_ecm).size(); +} + +////////////////////////////////////////////////// +uint64_t World::ActorCount(const EntityComponentManager &_ecm) const +{ + return this->Actors(_ecm).size(); +} + +////////////////////////////////////////////////// +uint64_t World::ModelCount(const EntityComponentManager &_ecm) const +{ + return this->Models(_ecm).size(); +} + diff --git a/src/World_TEST.cc b/src/World_TEST.cc new file mode 100644 index 0000000000..7c3a03d532 --- /dev/null +++ b/src/World_TEST.cc @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 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 "ignition/gazebo/World.hh" + +///////////////////////////////////////////////// +TEST(WorldTest, Constructor) +{ + ignition::gazebo::World worldNull; + EXPECT_EQ(ignition::gazebo::kNullEntity, worldNull.Entity()); + + ignition::gazebo::Entity id(3); + ignition::gazebo::World world(id); + + EXPECT_EQ(id, world.Entity()); +} + +///////////////////////////////////////////////// +TEST(WorldTest, CopyConstructor) +{ + ignition::gazebo::Entity id(3); + ignition::gazebo::World world(id); + + // Marked nolint because we are specifically testing copy + // constructor here (clang wants unnecessary copies removed) + ignition::gazebo::World worldCopy(world); // NOLINT + EXPECT_EQ(world.Entity(), worldCopy.Entity()); +} + +///////////////////////////////////////////////// +TEST(WorldTest, CopyAssignmentOperator) +{ + ignition::gazebo::Entity id(3); + ignition::gazebo::World world(id); + + ignition::gazebo::World worldCopy; + worldCopy = world; + EXPECT_EQ(world.Entity(), worldCopy.Entity()); +} + +///////////////////////////////////////////////// +TEST(WorldTest, MoveConstructor) +{ + ignition::gazebo::Entity id(3); + ignition::gazebo::World world(id); + + ignition::gazebo::World worldMoved(std::move(world)); + EXPECT_EQ(id, worldMoved.Entity()); +} + +///////////////////////////////////////////////// +TEST(WorldTest, MoveAssignmentOperator) +{ + ignition::gazebo::Entity id(3); + ignition::gazebo::World world(id); + + ignition::gazebo::World worldMoved; + worldMoved = std::move(world); + EXPECT_EQ(id, worldMoved.Entity()); +} diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index 2eec937a2e..57da2971c0 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -43,6 +43,7 @@ set(tests velocity_control_system.cc log_system.cc wind_effects.cc + world.cc ) # Tests that require a valid display diff --git a/test/integration/world.cc b/test/integration/world.cc new file mode 100644 index 0000000000..642c39cdda --- /dev/null +++ b/test/integration/world.cc @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2020 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ignition; +using namespace gazebo; + +class WorldIntegrationTest : public ::testing::Test +{ + public: void SetUp() override + { + ignition::common::Console::SetVerbosity(4); + } +}; + +////////////////////////////////////////////////// +TEST_F(WorldIntegrationTest, Valid) +{ + EntityComponentManager ecm; + + // No ID + { + World world; + EXPECT_FALSE(world.Valid(ecm)); + } + + // Missing world component + { + auto id = ecm.CreateEntity(); + World world(id); + EXPECT_FALSE(world.Valid(ecm)); + } + + // Valid + { + auto id = ecm.CreateEntity(); + ecm.CreateComponent(id, components::World()); + + World world(id); + EXPECT_TRUE(world.Valid(ecm)); + } +} + +////////////////////////////////////////////////// +TEST_F(WorldIntegrationTest, Name) +{ + EntityComponentManager ecm; + + auto id = ecm.CreateEntity(); + ecm.CreateComponent(id, components::World()); + + World world(id); + + EXPECT_EQ(std::nullopt, world.Name(ecm)); + + ecm.CreateComponent(id, components::Name("world_name")); + EXPECT_EQ("world_name", world.Name(ecm)); +} + +////////////////////////////////////////////////// +TEST_F(WorldIntegrationTest, Atmosphere) +{ + EntityComponentManager ecm; + + auto id = ecm.CreateEntity(); + ecm.CreateComponent(id, components::World()); + + World world(id); + + EXPECT_EQ(std::nullopt, world.Atmosphere(ecm)); + + ecm.CreateComponent(id, + components::Atmosphere(sdf::Atmosphere())); + auto atmosphere = world.Atmosphere(ecm).value(); + EXPECT_EQ(math::Temperature(288.15), atmosphere.Temperature()); +} + +////////////////////////////////////////////////// +TEST_F(WorldIntegrationTest, Gravity) +{ + EntityComponentManager ecm; + + auto id = ecm.CreateEntity(); + ecm.CreateComponent(id, components::World()); + + World world(id); + + EXPECT_EQ(std::nullopt, world.Gravity(ecm)); + + ecm.CreateComponent(id, components::Gravity({1, 2, 3})); + EXPECT_EQ(math::Vector3d(1, 2, 3), world.Gravity(ecm)); +} + +////////////////////////////////////////////////// +TEST_F(WorldIntegrationTest, MagneticField) +{ + EntityComponentManager ecm; + + auto id = ecm.CreateEntity(); + ecm.CreateComponent(id, components::World()); + + World world(id); + + // No gravity + EXPECT_EQ(std::nullopt, world.MagneticField(ecm)); + + // Add gravity + ecm.CreateComponent(id, + components::MagneticField({1, 2, 3})); + EXPECT_EQ(math::Vector3d(1, 2, 3), world.MagneticField(ecm)); +} + +////////////////////////////////////////////////// +TEST_F(WorldIntegrationTest, ModelByName) +{ + EntityComponentManager ecm; + + // World + auto eWorld = ecm.CreateEntity(); + World world(eWorld); + EXPECT_EQ(eWorld, world.Entity()); + EXPECT_EQ(0u, world.ModelCount(ecm)); + + // Model + auto eModel = ecm.CreateEntity(); + ecm.CreateComponent(eModel, components::Model()); + ecm.CreateComponent(eModel, + components::ParentEntity(eWorld)); + ecm.CreateComponent(eModel, + components::Name("model_name")); + + // Check world + EXPECT_EQ(eModel, world.ModelByName(ecm, "model_name")); + EXPECT_EQ(1u, world.ModelCount(ecm)); +} + +////////////////////////////////////////////////// +TEST_F(WorldIntegrationTest, LightByName) +{ + EntityComponentManager ecm; + + // World + auto eWorld = ecm.CreateEntity(); + World world(eWorld); + EXPECT_EQ(eWorld, world.Entity()); + EXPECT_EQ(0u, world.LightCount(ecm)); + + // Light + auto eLight = ecm.CreateEntity(); + ecm.CreateComponent(eLight, components::Light()); + ecm.CreateComponent(eLight, + components::ParentEntity(eWorld)); + ecm.CreateComponent(eLight, + components::Name("light_name")); + + // Check world + EXPECT_EQ(eLight, world.LightByName(ecm, "light_name")); + EXPECT_EQ(1u, world.LightCount(ecm)); +} + +////////////////////////////////////////////////// +TEST_F(WorldIntegrationTest, ActorByName) +{ + EntityComponentManager ecm; + + // World + auto eWorld = ecm.CreateEntity(); + World world(eWorld); + EXPECT_EQ(eWorld, world.Entity()); + EXPECT_EQ(0u, world.ActorCount(ecm)); + + // Actor + auto eActor = ecm.CreateEntity(); + ecm.CreateComponent(eActor, components::Actor()); + ecm.CreateComponent(eActor, + components::ParentEntity(eWorld)); + ecm.CreateComponent(eActor, + components::Name("actor_name")); + + // Check world + EXPECT_EQ(eActor, world.ActorByName(ecm, "actor_name")); + EXPECT_EQ(1u, world.ActorCount(ecm)); +} + diff --git a/tutorials.md.in b/tutorials.md.in index d6ace40092..84ef16ad3b 100644 --- a/tutorials.md.in +++ b/tutorials.md.in @@ -25,6 +25,7 @@ Ignition @IGN_DESIGNATION_CAP@ library and how to use the library effectively. **Migration from Gazebo-classic** * \subpage migrationplugins "Plugins": Walk through the differences between writing plugins for Gazebo-classic and Ignition Gazebo +* \subpage migrationworldapi "World API": Guide on what World C++ functions to call in Ignition Gazebo when migrating from Gazebo-classic * \subpage migrationmodelapi "Model API": Guide on what Model C++ functions to call in Ignition Gazebo when migrating from Gazebo-classic * \subpage ardupilot "Case Study": Migrating the ArduPilot ModelPlugin from Classic Gazebo to Ignition Gazebo. diff --git a/tutorials/migration_world_api.md b/tutorials/migration_world_api.md new file mode 100644 index 0000000000..f4fdc3b974 --- /dev/null +++ b/tutorials/migration_world_api.md @@ -0,0 +1,168 @@ +\page migrationworldapi + +# Migration from Gazebo-classic: World API + +When migrating plugins from Gazebo-classic to Ignition Gazebo, developers will +notice that the C++ APIs for both simulators are quite different. Be sure to +check the [plugin migration tutorial](migrationplugins.html) to get a high-level +view of the architecture differences before using this guide. + +This tutorial is meant to serve as a reference guide for developers migrating +functions from the +[gazebo::physics::World](http://osrf-distributions.s3.amazonaws.com/gazebo/api/11.0.0/classgazebo_1_1physics_1_1World.html) +class. + +If you're trying to use some API which doesn't have an equivalent on Ignition +yet, feel free to +[ticket an issue](https://github.com/ignitionrobotics/ign-gazebo/issues/). + +## World API + +Gazebo-classic's `gazebo::physics::World` provides lots of functionality, which +can be divided in these categories: + +* **Properties**: Setting / getting properties + * Example: [World::Gravity](http://osrf-distributions.s3.amazonaws.com/gazebo/api/11.0.0/classgazebo_1_1physics_1_1World.html#a700b50d9b34e470cb1b5fbbd33625b1e) / [World::SetGravity](http://osrf-distributions.s3.amazonaws.com/gazebo/api/11.0.0/classgazebo_1_1physics_1_1World.html#aff59ef61889e38ef3c2f09bda0c7cfbf) +* **Read family**: Getting children + * Example: [World::Lights](http://osrf-distributions.s3.amazonaws.com/gazebo/api/11.0.0/classgazebo_1_1physics_1_1World.html#a64a748a669cf5cb42bb3794afab6fa53) +* **Write family**: Adding children + * Example: [World::Clear](http://osrf-distributions.s3.amazonaws.com/gazebo/api/11.0.0/classgazebo_1_1physics_1_1World.html#aa71d36872f416feaa853788a7a7a7ef8) +* **Lifecycle**: Functions to control the world's lifecycle + * Example: [World::Step](http://osrf-distributions.s3.amazonaws.com/gazebo/api/11.0.0/classgazebo_1_1physics_1_1World.html#af272078d98d7f24a1f8949993d2d5493) +* **Others**: Functions that don't fit any of the categories above + * Example: [World::UniqueModelName](http://osrf-distributions.s3.amazonaws.com/gazebo/api/11.0.0/classgazebo_1_1physics_1_1World.html#a05934164af0cf95eb5a4be70e726846d) + +You'll find the Ignition APIs below on the following headers: + +* [ignition/gazebo/World.hh](https://ignitionrobotics.org/api/gazebo/3.3/World_8hh.html) +* [ignition/gazebo/Util.hh](https://ignitionrobotics.org/api/gazebo/3.3/Util_8hh.html) +* [ignition/gazebo/SdfEntityCreator.hh](https://ignitionrobotics.org/api/gazebo/3.3/SdfEntityCreator_8hh.html) + +It's worth remembering that most of this functionality can be performed using +the +[EntityComponentManager](https://ignitionrobotics.org/api/gazebo/3.3/classignition_1_1gazebo_1_1EntityComponentManager.html) +directly. The functions presented here exist for convenience and readability. + +### Properties + +Most of Gazebo-classic's World API is related to setting and getting +properties. These functions are great candidates to have equivalents on Ignition +Gazebo, because the Entity-Component-System architecture is perfect for setting +components (properties) into entities such as worlds. + +Classic | Ignition +-- | -- +Atmosphere | `ignition::gazebo::World::Atmosphere` +AtmosphereEnabled | TODO +DisableAllModels | TODO +EnableAllModels | TODO +GetSDFDom | TODO +Gravity | `ignition::gazebo::World::Gravity` +IsLoaded | Not applicable +IsPaused | Use `ignition::gazebo::UpdateInfo` +Iterations | Use `ignition::gazebo::UpdateInfo` +MagneticField | `ignition::gazebo::World::MagneticField` +Name | `ignition::gazebo::World::Name` +PauseTime | Use `ignition::gazebo::UpdateInfo` +Physics | TODO +PhysicsEnabled | TODO +PresetMgr | TODO +PublishLightPose | Use `ignition::gazebo::systems::PosePublisher` +PublishModelPose | Use `ignition::gazebo::systems::PosePublisher` +PublishModelScale | TODO +RealTime | Use `ignition::gazebo::UpdateInfo` +Running | Not applicable +SDF | TODO +SetAtmosphereEnabled | TODO +SetGravity | TODO +SetGravitySDF | TODO +SetMagneticField | TODO +SetPaused | Use world control service +SetPhysicsEnabled | TODO +SetSimTime | Use world control service +SetState | TODO +SetWindEnabled | TODO +SimTime | Use `ignition::gazebo::UpdateInfo` +SphericalCoords | TODO +StartTime | Use `ignition::gazebo::UpdateInfo` +URI | TODO +UpdateStateSDF | TODO +Wind | TODO +WindEnabled | TODO + +## Read family + +These APIs deal with reading information related to child / parent +relationships. + +The main difference in these APIs across Gazebo generations is that +on classic, they deal with shared pointers to entities, while on Ignition, +they deal with entity IDs. + +Classic | Ignition +-- | -- +BaseByName | Use type-specific `ignition::gazebo::World::*ByName` +EntityByName | Use type-specific `ignition::gazebo::World::*ByName` +LightByName | `ignition::gazebo::World::LightByName` +LightCount | `ignition::gazebo::World::LightCount` +Lights | `ignition::gazebo::World::Lights` +ModelByIndex | `ignition::gazebo::World::ModelByName` +ModelByName | `ignition::gazebo::World::ModelByName` +ModelCount | `ignition::gazebo::World::ModelCount` +Models | `ignition::gazebo::World::Models` +PrintEntityTree | Use scene graph service + +## Write family + +These functions deal with modifying the entity tree, attaching children to new +parents. + +Classic | Ignition +-- | -- +Clear | TODO +ClearModels | TODO +InsertModelFile | TODO +InsertModelSDF | `ignition::gazebo::SdfEntityCreator::CreateEntities` +InsertModelString | TODO +RemoveModel | TODO +RemovePlugin | TODO + +## Lifecycle + +These functions aren't related to the state of a model, but perform some +processing related to the model's lifecycle, like initializing, updating or +terminating it. + +Classic | Ignition +-- | -- +Fini | N/A +Init | N/A +Load | `ignition::gazebo::SdfEntityCreator::CreateEntities` +LoadLight | `ignition::gazebo::SdfEntityCreator::CreateEntities` +LoadPlugin | TODO +Reset | TODO +ResetEntities | TODO +ResetPhysicsStates | TODO +ResetTime | Use world control service +Run | See server API +RunBlocking | See server API +SensorsInitialized | N/A +Step | See server API +Stop | See server API +_AddDirty | N/A +_SetSensorsInitialized | N/A + +## Others + +Miscelaneous functions that don't fit the other categories. Most of them involve +logic that should be performed from within a system. + +Classic | Ignition +-- | -- +EntityBelowPoint | Requires a system +ModelBelowPoint | Requires a system +SceneMsg | Use `ignition::gazebo::systems::SceneBoradcaster` +WorldPoseMutex | N/A +StripWorldName | N/A +UniqueModelName | TODO +Save | Use SDF generator From 88ad6319503e8f518a88827d02ba6b2e7b0bffa1 Mon Sep 17 00:00:00 2001 From: Louise Poubel Date: Wed, 14 Oct 2020 20:01:24 -0700 Subject: [PATCH 2/3] Use component type Signed-off-by: Louise Poubel --- .../ignition/gazebo/EntityComponentManager.hh | 6 ++++-- .../gazebo/detail/EntityComponentManager.hh | 6 +++--- src/EntityComponentManager_TEST.cc | 16 ++++++++-------- src/Link.cc | 14 ++++++-------- src/World.cc | 11 ++++------- 5 files changed, 25 insertions(+), 28 deletions(-) diff --git a/include/ignition/gazebo/EntityComponentManager.hh b/include/ignition/gazebo/EntityComponentManager.hh index 2bf99ad36d..9a0a417de9 100644 --- a/include/ignition/gazebo/EntityComponentManager.hh +++ b/include/ignition/gazebo/EntityComponentManager.hh @@ -219,10 +219,12 @@ namespace ignition /// * If the entity doesn't have that component, it will return nullopt. /// * If the entity has the component, return its data. /// \param[in] _entity The entity. + /// \tparam ComponentTypeT Component type /// \return The data of the component of the specified type assigned to /// specified Entity, or nullptr if the component could not be found. - public: template - std::optional ComponentData(const Entity _entity) const; + public: template + std::optional ComponentData( + const Entity _entity) const; /// \brief Get the type IDs of all components attached to an entity. /// \param[in] _entity Entity to check. diff --git a/include/ignition/gazebo/detail/EntityComponentManager.hh b/include/ignition/gazebo/detail/EntityComponentManager.hh index 02f1b6cf96..0ea2777849 100644 --- a/include/ignition/gazebo/detail/EntityComponentManager.hh +++ b/include/ignition/gazebo/detail/EntityComponentManager.hh @@ -79,9 +79,9 @@ ComponentTypeT *EntityComponentManager::Component(const ComponentKey &_key) } ////////////////////////////////////////////////// -template -std::optional EntityComponentManager::ComponentData( - const Entity _entity) const +template +std::optional + EntityComponentManager::ComponentData(const Entity _entity) const { auto comp = this->Component(_entity); if (!comp) diff --git a/src/EntityComponentManager_TEST.cc b/src/EntityComponentManager_TEST.cc index a8db085b1b..a87b6e9666 100644 --- a/src/EntityComponentManager_TEST.cc +++ b/src/EntityComponentManager_TEST.cc @@ -461,7 +461,7 @@ TEST_P(EntityComponentManagerFixture, ComponentValues) ASSERT_NE(nullptr, value); EXPECT_EQ(123, value->Data()); - auto data = manager.ComponentData(eInt); + auto data = manager.ComponentData(eInt); EXPECT_EQ(123, data); } @@ -470,7 +470,7 @@ TEST_P(EntityComponentManagerFixture, ComponentValues) ASSERT_NE(nullptr, value); EXPECT_DOUBLE_EQ(0.123, value->Data()); - auto data = manager.ComponentData(eDouble); + auto data = manager.ComponentData(eDouble); EXPECT_EQ(0.123, data); } @@ -479,7 +479,7 @@ TEST_P(EntityComponentManagerFixture, ComponentValues) ASSERT_NE(nullptr, value); EXPECT_EQ(456, value->Data()); - auto data = manager.ComponentData(eIntDouble); + auto data = manager.ComponentData(eIntDouble); EXPECT_EQ(456, data); } @@ -488,7 +488,7 @@ TEST_P(EntityComponentManagerFixture, ComponentValues) ASSERT_NE(nullptr, value); EXPECT_DOUBLE_EQ(0.456, value->Data()); - auto data = manager.ComponentData(eIntDouble); + auto data = manager.ComponentData(eIntDouble); EXPECT_EQ(0.456, data); } @@ -497,7 +497,7 @@ TEST_P(EntityComponentManagerFixture, ComponentValues) const auto *value = manager.Component(eDouble); ASSERT_EQ(nullptr, value); - auto data = manager.ComponentData(eDouble); + auto data = manager.ComponentData(eDouble); EXPECT_EQ(std::nullopt, data); } @@ -505,7 +505,7 @@ TEST_P(EntityComponentManagerFixture, ComponentValues) const auto *value = manager.Component(eInt); ASSERT_EQ(nullptr, value); - auto data = manager.ComponentData(eInt); + auto data = manager.ComponentData(eInt); EXPECT_EQ(std::nullopt, data); } @@ -513,7 +513,7 @@ TEST_P(EntityComponentManagerFixture, ComponentValues) const auto *value = manager.Component(999); ASSERT_EQ(nullptr, value); - auto data = manager.ComponentData(999); + auto data = manager.ComponentData(999); EXPECT_EQ(std::nullopt, data); } @@ -521,7 +521,7 @@ TEST_P(EntityComponentManagerFixture, ComponentValues) const auto *value = manager.Component(999); ASSERT_EQ(nullptr, value); - auto data = manager.ComponentData(999); + auto data = manager.ComponentData(999); EXPECT_EQ(std::nullopt, data); } } diff --git a/src/Link.cc b/src/Link.cc index df54643dc7..6289b7909f 100644 --- a/src/Link.cc +++ b/src/Link.cc @@ -94,7 +94,7 @@ bool Link::Valid(const EntityComponentManager &_ecm) const ////////////////////////////////////////////////// std::optional Link::Name(const EntityComponentManager &_ecm) const { - return _ecm.ComponentData(this->dataPtr->id); + return _ecm.ComponentData(this->dataPtr->id); } ////////////////////////////////////////////////// @@ -177,8 +177,7 @@ bool Link::WindMode(const EntityComponentManager &_ecm) const std::optional Link::WorldPose( const EntityComponentManager &_ecm) const { - return _ecm.ComponentData( - this->dataPtr->id); + return _ecm.ComponentData(this->dataPtr->id); } ////////////////////////////////////////////////// @@ -198,8 +197,7 @@ std::optional Link::WorldInertialPose( std::optional Link::WorldLinearVelocity( const EntityComponentManager &_ecm) const { - return _ecm.ComponentData( - this->dataPtr->id); + return _ecm.ComponentData(this->dataPtr->id); } ////////////////////////////////////////////////// @@ -226,7 +224,7 @@ std::optional Link::WorldLinearVelocity( std::optional Link::WorldAngularVelocity( const EntityComponentManager &_ecm) const { - return _ecm.ComponentData( + return _ecm.ComponentData( this->dataPtr->id); } @@ -234,8 +232,8 @@ std::optional Link::WorldAngularVelocity( std::optional Link::WorldLinearAcceleration( const EntityComponentManager &_ecm) const { - return _ecm.ComponentData(this->dataPtr->id); + return _ecm.ComponentData( + this->dataPtr->id); } ////////////////////////////////////////////////// diff --git a/src/World.cc b/src/World.cc index 01b908a757..0a1da42c34 100644 --- a/src/World.cc +++ b/src/World.cc @@ -81,31 +81,28 @@ bool World::Valid(const EntityComponentManager &_ecm) const ////////////////////////////////////////////////// std::optional World::Name(const EntityComponentManager &_ecm) const { - return _ecm.ComponentData(this->dataPtr->id); + return _ecm.ComponentData(this->dataPtr->id); } ////////////////////////////////////////////////// std::optional World::Atmosphere( const EntityComponentManager &_ecm) const { - return _ecm.ComponentData( - this->dataPtr->id); + return _ecm.ComponentData(this->dataPtr->id); } ////////////////////////////////////////////////// std::optional World::Gravity( const EntityComponentManager &_ecm) const { - return _ecm.ComponentData( - this->dataPtr->id); + return _ecm.ComponentData(this->dataPtr->id); } ////////////////////////////////////////////////// std::optional World::MagneticField( const EntityComponentManager &_ecm) const { - return _ecm.ComponentData( - this->dataPtr->id); + return _ecm.ComponentData(this->dataPtr->id); } ////////////////////////////////////////////////// From 6a8a6d0edc45841e418e0f23b412466ac0b9516a Mon Sep 17 00:00:00 2001 From: Louise Poubel Date: Thu, 15 Oct 2020 15:12:17 -0700 Subject: [PATCH 3/3] PR feedback Signed-off-by: Louise Poubel --- include/ignition/gazebo/World.hh | 15 ++++++--------- src/World.cc | 6 +++--- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/include/ignition/gazebo/World.hh b/include/ignition/gazebo/World.hh index 9fdf511b36..1e902ef169 100644 --- a/include/ignition/gazebo/World.hh +++ b/include/ignition/gazebo/World.hh @@ -92,9 +92,9 @@ namespace ignition /// \return True if it's a valid world in the manager. public: bool Valid(const EntityComponentManager &_ecm) const; - /// \brief Get the link's unscoped name. + /// \brief Get the world's unscoped name. /// \param[in] _ecm Entity-component manager. - /// \return Link's name or nullopt if the entity does not have a + /// \return World's name or nullopt if the entity does not have a /// components::Name component public: std::optional Name( const EntityComponentManager &_ecm) const; @@ -106,7 +106,7 @@ namespace ignition public: std::optional Gravity( const EntityComponentManager &_ecm) const; - /// \brief Get the magnetic field in T. + /// \brief Get the magnetic field in Tesla. /// \param[in] _ecm Entity-component manager. /// \return Magnetic field vector or nullopt if the entity does not /// have a components::MagneticField component. @@ -125,27 +125,24 @@ namespace ignition /// \param[in] _ecm Entity-component manager. /// \param[in] _name Light name. /// \return Light entity. - /// \todo(anyone) Make const public: gazebo::Entity LightByName(const EntityComponentManager &_ecm, - const std::string &_name); + const std::string &_name) const; /// \brief Get the ID of a actor entity which is an immediate child of /// this world. /// \param[in] _ecm Entity-component manager. /// \param[in] _name Actor name. /// \return Actor entity. - /// \todo(anyone) Make const public: gazebo::Entity ActorByName(const EntityComponentManager &_ecm, - const std::string &_name); + const std::string &_name) const; /// \brief Get the ID of a model entity which is an immediate child of /// this world. /// \param[in] _ecm Entity-component manager. /// \param[in] _name Model name. /// \return Model entity. - /// \todo(anyone) Make const public: gazebo::Entity ModelByName(const EntityComponentManager &_ecm, - const std::string &_name); + const std::string &_name) const; /// \brief Get all lights which are immediate children of this world. /// \param[in] _ecm Entity-component manager. diff --git a/src/World.cc b/src/World.cc index 0a1da42c34..333a0f430c 100644 --- a/src/World.cc +++ b/src/World.cc @@ -107,7 +107,7 @@ std::optional World::MagneticField( ////////////////////////////////////////////////// Entity World::LightByName(const EntityComponentManager &_ecm, - const std::string &_name) + const std::string &_name) const { // Can't use components::Light in EntityByComponents, see // https://github.com/ignitionrobotics/ign-gazebo/issues/376 @@ -125,7 +125,7 @@ Entity World::LightByName(const EntityComponentManager &_ecm, ////////////////////////////////////////////////// Entity World::ActorByName(const EntityComponentManager &_ecm, - const std::string &_name) + const std::string &_name) const { // Can't use components::Actor in EntityByComponents, see // https://github.com/ignitionrobotics/ign-gazebo/issues/376 @@ -143,7 +143,7 @@ Entity World::ActorByName(const EntityComponentManager &_ecm, ////////////////////////////////////////////////// Entity World::ModelByName(const EntityComponentManager &_ecm, - const std::string &_name) + const std::string &_name) const { return _ecm.EntityByComponents( components::ParentEntity(this->dataPtr->id),