diff --git a/modules/core/inc/tactile/core/map/layer/object_layer.hpp b/modules/core/inc/tactile/core/map/layer/object_layer.hpp index 6974965f38..146e6f8119 100644 --- a/modules/core/inc/tactile/core/map/layer/object_layer.hpp +++ b/modules/core/inc/tactile/core/map/layer/object_layer.hpp @@ -3,8 +3,13 @@ #pragma once #include "tactile/core/api.hpp" +#include "tactile/core/container/smart_ptr.hpp" +#include "tactile/core/container/tree_map.hpp" +#include "tactile/core/container/vector.hpp" #include "tactile/core/map/layer/layer.hpp" #include "tactile/core/map/layer/layer_behavior_delegate.hpp" +#include "tactile/core/map/layer/object.hpp" +#include "tactile/core/misc/id_types.hpp" #include "tactile/core/prelude.hpp" namespace tactile { @@ -18,12 +23,79 @@ class TACTILE_CORE_API ObjectLayer final : public ILayer { void accept(ILayerVisitor& visitor) override; + /** + * \brief Adds an object to the layer. + * + * \note An existing object in the layer with the specified ID will be overwritten. + * + * \param id the ID to associate with the object. + * \param object the object to add. + */ + void add_object(ObjectID id, Shared<Object> object); + + /** + * \brief Removes an object from the layer. + * + * \param id the ID associated with the object. + * + * \return the removed object. + */ + auto remove_object(ObjectID id) -> Shared<Object>; + void set_persistent_id(Maybe<int32> id) override; void set_opacity(float opacity) override; void set_visible(bool visible) override; + /** + * \brief Returns an object in the layer. + * + * \note Avoid calling this function if you only need to "look" at the returned object, + * prefer calling `find_object` in such cases. This avoids expensive copies of + * shared pointers. + * + * \param id the ID of the desired object. + * + * \return a shared pointer to the found object. + */ + [[nodiscard]] + auto get_object(ObjectID id) -> Shared<Object>; + + /** + * \brief Attempts to find and return an object in the layer. + * + * \param id the ID of the desired object. + * + * \return a pointer to the found object, or a null pointer. + */ + [[nodiscard]] + auto find_object(ObjectID id) -> Object*; + + /** + * \copydoc find_object() + */ + [[nodiscard]] + auto find_object(ObjectID id) const -> const Object*; + + /** + * \brief Indicates whether the layer contains an object with the specified ID. + * + * \param id the object ID to check. + * + * \return true if an object was found; false otherwise. + */ + [[nodiscard]] + auto has_object(ObjectID id) const -> bool; + + /** + * \brief Returns the number of objects in the layer. + * + * \return an object count. + */ + [[nodiscard]] + auto object_count() const -> usize; + [[nodiscard]] auto get_persistent_id() const -> Maybe<int32> override; @@ -44,6 +116,7 @@ class TACTILE_CORE_API ObjectLayer final : public ILayer { private: LayerBehaviorDelegate mDelegate; + TreeMap<ObjectID, Shared<Object>> mObjects; }; } // namespace tactile diff --git a/modules/core/src/tactile/core/map/layer/object_layer.cpp b/modules/core/src/tactile/core/map/layer/object_layer.cpp index ecbe85d4ec..49e605d450 100644 --- a/modules/core/src/tactile/core/map/layer/object_layer.cpp +++ b/modules/core/src/tactile/core/map/layer/object_layer.cpp @@ -2,6 +2,9 @@ #include "tactile/core/map/layer/object_layer.hpp" +#include <utility> // move + +#include "tactile/core/container/lookup.hpp" #include "tactile/core/map/layer/layer_visitor.hpp" namespace tactile { @@ -16,6 +19,20 @@ void ObjectLayer::accept(ILayerVisitor& visitor) visitor.visit(*this); } +void ObjectLayer::add_object(const ObjectID id, Shared<Object> object) +{ + mObjects[id] = std::move(object); +} + +auto ObjectLayer::remove_object(const ObjectID id) -> Shared<Object> +{ + if (auto object = erase_from(mObjects, id)) { + return std::move(object).value(); + } + + return nullptr; +} + void ObjectLayer::set_persistent_id(const Maybe<int32> id) { mDelegate.set_persistent_id(id); @@ -31,6 +48,33 @@ void ObjectLayer::set_visible(const bool visible) mDelegate.set_visible(visible); } +auto ObjectLayer::get_object(const ObjectID id) -> Shared<Object> +{ + return lookup_in(mObjects, id); +} + +auto ObjectLayer::find_object(const ObjectID id) -> Object* +{ + const auto iter = mObjects.find(id); + return (iter != mObjects.end()) ? iter->second.get() : nullptr; +} + +auto ObjectLayer::find_object(const ObjectID id) const -> const Object* +{ + const auto iter = mObjects.find(id); + return (iter != mObjects.end()) ? iter->second.get() : nullptr; +} + +auto ObjectLayer::has_object(const ObjectID id) const -> bool +{ + return mObjects.contains(id); +} + +auto ObjectLayer::object_count() const -> usize +{ + return mObjects.size(); +} + auto ObjectLayer::get_persistent_id() const -> Maybe<int32> { return mDelegate.get_persistent_id(); diff --git a/modules/core/test/map/layer/object_layer_test.cpp b/modules/core/test/map/layer/object_layer_test.cpp new file mode 100644 index 0000000000..f02f839e8e --- /dev/null +++ b/modules/core/test/map/layer/object_layer_test.cpp @@ -0,0 +1,89 @@ +// Copyright (C) 2023 Albin Johansson (GNU General Public License v3.0) + +#include "tactile/core/map/layer/object_layer.hpp" + +#include <gtest/gtest.h> + +using namespace tactile; +using tactile::int_literals::operator""_uz; + +/// \tests tactile::ObjectLayer::add_object +TEST(ObjectLayer, AddObject) +{ + const ObjectID object_id {42}; + + const auto object1 = make_shared<Object>(ObjectType::kRect); + const auto object2 = make_shared<Object>(ObjectType::kEllipse); + + ObjectLayer layer; + EXPECT_EQ(layer.object_count(), 0_uz); + + layer.add_object(object_id, object1); + EXPECT_EQ(layer.object_count(), 1_uz); + EXPECT_EQ(layer.get_object(object_id), object1); + + layer.add_object(object_id, object2); + EXPECT_EQ(layer.object_count(), 1_uz); + EXPECT_EQ(layer.get_object(object_id), object2); +} + +/// \tests tactile::ObjectLayer::remove_object +/// \tests tactile::ObjectLayer::has_object +TEST(ObjectLayer, RemoveObject) +{ + const ObjectID object1_id {212}; + const ObjectID object2_id {832}; + + const auto object1 = make_shared<Object>(ObjectType::kPoint); + const auto object2 = make_shared<Object>(ObjectType::kRect); + + ObjectLayer layer; + layer.add_object(object1_id, object1); + layer.add_object(object2_id, object2); + ASSERT_EQ(layer.object_count(), 2_uz); + EXPECT_TRUE(layer.has_object(object1_id)); + EXPECT_TRUE(layer.has_object(object2_id)); + + EXPECT_EQ(layer.remove_object(object1_id), object1); + EXPECT_EQ(layer.object_count(), 1_uz); + EXPECT_FALSE(layer.has_object(object1_id)); + EXPECT_TRUE(layer.has_object(object2_id)); + + EXPECT_EQ(layer.remove_object(object2_id), object2); + EXPECT_EQ(layer.object_count(), 0_uz); + EXPECT_FALSE(layer.has_object(object1_id)); + EXPECT_FALSE(layer.has_object(object2_id)); + + EXPECT_EQ(layer.remove_object(object1_id), nullptr); + EXPECT_EQ(layer.remove_object(object2_id), nullptr); +} + +/// \tests tactile::ObjectLayer::get_object +TEST(ObjectLayer, GetObject) +{ + const ObjectID object_id {123}; + const auto object = make_shared<Object>(ObjectType::kPoint); + + ObjectLayer layer; + EXPECT_ANY_THROW((void) layer.get_object(object_id)); + + layer.add_object(object_id, object); + EXPECT_EQ(layer.get_object(object_id), object); +} + +/// \tests tactile::ObjectLayer::find_object +TEST(ObjectLayer, FindObject) +{ + const ObjectID object_id {923}; + const auto object = make_shared<Object>(ObjectType::kEllipse); + + ObjectLayer layer; + const auto& const_layer = layer; + + EXPECT_EQ(layer.find_object(object_id), nullptr); + EXPECT_EQ(const_layer.find_object(object_id), nullptr); + + layer.add_object(object_id, object); + EXPECT_EQ(layer.find_object(object_id), object.get()); + EXPECT_EQ(const_layer.find_object(object_id), object.get()); +}