Skip to content

Commit

Permalink
Add Documentation to selectables registry.
Browse files Browse the repository at this point in the history
Also added raw iterators.
  • Loading branch information
5cript committed Sep 20, 2023
1 parent 9d66fc9 commit 9030ae3
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 3 deletions.
169 changes: 166 additions & 3 deletions nui/include/nui/data_structures/selectables_registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,34 @@ namespace Nui
{};
}

/**
* This container associates items with an id and allows for individual items to be "selected",
* which removes them from the container and stores them in a separate container. This allows
* for efficient iteration over the selected items. Selected items can be reinserted into the
* container via deselect (or not when the deselect callback returns false).
*
* @tparam T Type of the items to store.
*/
template <typename T>
class SelectablesRegistry
{
public:
/// @brief Id type used to identify items.
using IdType = std::size_t;

/**
* @brief Wrapper around items that associates them with an id.
*/
struct ItemWithId
{
/// @brief Id of the item.
IdType id;

/**
* @brief The item.
*
* The item is stored in an optional to allow for efficient "removal" of items from the core container.
*/
std::optional<T> item;

template <typename... Args>
Expand All @@ -47,14 +67,26 @@ namespace Nui
ItemWithId& operator=(ItemWithId const&) = default;
ItemWithId& operator=(ItemWithId&&) = default;

/// @brief Compares the id of the item.
bool operator<(ItemWithId const& other) const
{
return id < other.id;
}
};

/// @brief Type of the container that stores the items.
using ItemContainerType = std::vector<ItemWithId>;

/// @brief Invalid id value.
constexpr static auto invalidId = std::numeric_limits<IdType>::max();

/**
* @brief Iterator that ignores items that are selected.
*
* This iterator is also a safe iterator, performing range checking, which is required but also adds overhead.
*
* @tparam WrappedIterator The underlying iterator type.
*/
template <typename WrappedIterator>
class IteratorBase
{
Expand Down Expand Up @@ -218,6 +250,12 @@ namespace Nui
SelectablesRegistry& operator=(SelectablesRegistry&&) = default;
~SelectablesRegistry() = default;

/**
* @brief Append an item to the container.
*
* @param element A new item to append.
* @return IdType The id of the new item.
*/
IdType append(T const& element)
{
items_.push_back(ItemWithId{id_, std::optional<T>{element}});
Expand All @@ -228,6 +266,13 @@ namespace Nui
id_ = 0;
return id;
}

/**
* @brief Append an item to the container.
*
* @param element A new item to append.
* @return IdType The id of the new item.
*/
IdType append(T&& element)
{
items_.push_back(ItemWithId{id_, std::optional<T>{std::move(element)}});
Expand All @@ -239,6 +284,13 @@ namespace Nui
return id;
}

/**
* @brief Emplace an item to the container.
*
* @tparam Args Types of the arguments to forward to the constructor of the item.
* @param args Arguments to forward to the constructor of the item.
* @return IdType The id of the new item.
*/
template <typename... Args>
IdType emplace(Args&&... args)
{
Expand All @@ -251,6 +303,12 @@ namespace Nui
return id;
}

/**
* @brief Erase/Remove an item from the container.
*
* @param id Id of the item to erase.
* @return IteratorType Iterator to the next item.
*/
IteratorType erase(IdType id)
{
const auto p = findItem(id);
Expand All @@ -264,6 +322,12 @@ namespace Nui
return {result, items_.begin(), items_.end()};
}

/**
* @brief Erase/Remove an item from the container and return it.
*
* @param id Id of the item to get and erase.
* @return std::optional<T> The erased item.
*/
std::optional<T> pop(IdType id)
{
const auto p = findItem(id);
Expand All @@ -280,10 +344,22 @@ namespace Nui

struct SelectionResult
{
/// @brief Pointer to the selected item (may be nullptr).
std::optional<T>* item;

/// @brief Whether the item was found.
bool found;

/// @brief Whether the item was already selected.
bool alreadySelected;
};

/**
* @brief Select an item.
*
* @param id The id of the item to select.
* @return SelectionResult The result of the selection.
*/
SelectionResult select(IdType id)
{
const auto iter = findItem(id);
Expand Down Expand Up @@ -359,6 +435,12 @@ namespace Nui
return false;
}

/**
* @brief Get iterator to item with id.
*
* @param id Id of the item to get.
* @return IteratorType Iterator to the item.
*/
IteratorType get(IdType id)
{
auto iter = findItem(id);
Expand All @@ -367,6 +449,12 @@ namespace Nui
return IteratorType{iter, items_.begin(), items_.end()};
}

/**
* @brief Get iterator to item with id.
*
* @param id Id of the item to get.
* @return ConstIteratorType Iterator to the item.
*/
ConstIteratorType get(IdType id) const
{
auto iter = findItem(id);
Expand All @@ -375,51 +463,126 @@ namespace Nui
return ConstIteratorType{iter, items_.begin(), items_.end()};
}

/**
* @brief Returns item by id.
*
* @param id Id of the item to get.
* @return auto const& Reference to the item.
*/
auto const& operator[](IdType id) const
{
return *get(id);
}

/**
* @brief Returns item by id.
*
* @param id Id of the item to get.
* @return auto& Reference to the item.
*/
auto& operator[](IdType id)
{
return *get(id);
}

/**
* @brief Returns iterator to first unselected item or end.
*/
IteratorType begin()
{
return {items_.begin(), items_.begin(), items_.end()};
}

/**
* @brief Returns iterator to first unselected item or end.
*/
ConstIteratorType begin() const
{
return {items_.begin(), items_.begin(), items_.end()};
}

/**
* @brief Returns iterator to first unselected item or end.
*/
ConstIteratorType cbegin() const
{
return {items_.cbegin(), items_.begin(), items_.end()};
}

/**
* @brief Returns end iterator
*/
IteratorType end()
{
return {items_.end(), items_.begin(), items_.end()};
}

/**
* @brief Returns end iterator
*/
ConstIteratorType end() const
{
return {items_.end(), items_.begin(), items_.end()};
}

/**
* @brief Returns end iterator
*/
ConstIteratorType cend() const
{
return {items_.cend(), items_.begin(), items_.end()};
}

/**
* @brief Returns whether the container is empty.
*/
bool empty() const
{
return itemCount_ == 0;
}

/**
* @brief Returns the amount of items in the container.
*/
std::size_t size() const
{
return itemCount_;
}

/**
* @brief Returns an iterator to the underlying container.
*/
ItemContainerType::iterator rawBegin()
{
return items_.begin();
}

/**
* @brief Returns an iterator to the underlying container.
*/
ItemContainerType::iterator rawEnd()
{
return items_.end();
}

/**
* @brief Returns a const iterator to the underlying container.
*/
ItemContainerType::const_iterator rawBegin() const
{
return items_.begin();
}

/**
* @brief Returns a const iterator to the underlying container.
*/
ItemContainerType::const_iterator rawEnd() const
{
return items_.end();
}

private:
typename std::vector<ItemWithId>::iterator findItem(IdType id)
typename ItemContainerType::iterator findItem(IdType id)
{
const auto p =
std::lower_bound(std::begin(items_), std::end(items_), id, [](auto const& lhs, auto const& rhs) {
Expand All @@ -430,7 +593,7 @@ namespace Nui
return std::end(items_);
return p;
}
typename std::vector<ItemWithId>::const_iterator findItem(IdType id) const
typename ItemContainerType::const_iterator findItem(IdType id) const
{
const auto p =
std::lower_bound(std::begin(items_), std::end(items_), id, [](auto const& lhs, auto const& rhs) {
Expand Down Expand Up @@ -459,7 +622,7 @@ namespace Nui
}

private:
std::vector<ItemWithId> items_{};
ItemContainerType items_{};
// TODO: improve performance, id link backs are costly, each one is a binary search.
std::set<ItemWithId> selected_{};
IdType itemCount_{0};
Expand Down
21 changes: 21 additions & 0 deletions nui/test/nui/test_selectables_registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -572,4 +572,25 @@ namespace Nui::Tests
EXPECT_EQ(registry.size(), idCount);
EXPECT_FALSE(registry.empty());
}

TEST_F(TestSelectablesRegistry, RawIteratorsIterateEvenSelectedItems)
{
constexpr auto idCount = 100;

std::vector<decltype(registry)::IdType> ids;
for (int i = 0; i != idCount; ++i)
{
auto id = registry.emplace("?");
registry[id].data = std::to_string(id);
ids.push_back(id);
}

std::shuffle(ids.begin(), ids.end(), engine);
for (std::size_t i = 0; i != ids.size() / 2; ++i)
{
registry.select(ids[i]);
}

EXPECT_EQ(std::distance(registry.rawBegin(), registry.rawEnd()), idCount);
}
}

0 comments on commit 9030ae3

Please sign in to comment.