graph: (currently directed only) adjacency_matrix

This commit is contained in:
Michele Caini
2022-06-04 10:52:28 +02:00
parent a6171551bd
commit 8029777c4e
8 changed files with 613 additions and 0 deletions

View File

@@ -147,6 +147,8 @@ if(ENTT_INCLUDE_HEADERS)
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/sparse_set.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/storage.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/view.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/adjacency_matrix.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/fwd.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/locator/locator.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/adl_pointer.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/container.hpp>

1
TODO
View File

@@ -12,6 +12,7 @@ DOC:
* document iterator.hpp (see core.md)
WIP:
* add directed/undirected support to adjacency matrix (see brief)
* handle: replace visit with an iterable object
* add storage getter for filters to views and groups
* exploit the tombstone mechanism to allow enabling/disabling entities (see bump, compact and clear for further details)

View File

@@ -32,6 +32,7 @@
#include "entity/sparse_set.hpp"
#include "entity/storage.hpp"
#include "entity/view.hpp"
#include "graph/adjacency_matrix.hpp"
#include "locator/locator.hpp"
#include "meta/adl_pointer.hpp"
#include "meta/container.hpp"

View File

@@ -1,6 +1,7 @@
#include "container/fwd.hpp"
#include "core/fwd.hpp"
#include "entity/fwd.hpp"
#include "graph/fwd.hpp"
#include "meta/fwd.hpp"
#include "poly/fwd.hpp"
#include "process/fwd.hpp"

View File

@@ -0,0 +1,298 @@
#ifndef ENTT_GRAPH_ADJACENCY_MATRIX_HPP
#define ENTT_GRAPH_ADJACENCY_MATRIX_HPP
#include <cstddef>
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../core/iterator.hpp"
#include "fwd.hpp"
namespace entt {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
struct edge_iterator {
using value_type = std::pair<std::size_t, std::size_t>;
using pointer = input_iterator_pointer<value_type>;
using reference = value_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
constexpr edge_iterator() noexcept
: matrix{},
vert{},
it{},
last{} {}
constexpr edge_iterator(const std::size_t *ref, const std::size_t vertices, const std::size_t from) noexcept
: edge_iterator{ref, vertices, from, vertices * vertices} {}
constexpr edge_iterator(const std::size_t *ref, const std::size_t vertices, const std::size_t from, const std::size_t to) noexcept
: matrix{ref},
vert{vertices},
it{from},
last{to} {
for(; it != last && !matrix[it]; ++it) {}
}
constexpr edge_iterator &operator++() noexcept {
for(++it; it != last && !matrix[it]; ++it) {}
return *this;
}
constexpr edge_iterator operator++(int) noexcept {
edge_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return *operator->();
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return std::make_pair<std::size_t>(it / vert, it % vert);
}
friend constexpr bool operator==(const edge_iterator &, const edge_iterator &) noexcept;
private:
const std::size_t *matrix;
std::size_t vert;
std::size_t it;
std::size_t last;
};
[[nodiscard]] inline constexpr bool operator==(const edge_iterator &lhs, const edge_iterator &rhs) noexcept {
return lhs.it == rhs.it;
}
[[nodiscard]] inline constexpr bool operator!=(const edge_iterator &lhs, const edge_iterator &rhs) noexcept {
return !(lhs == rhs);
}
} // namespace internal
/**
* Internal details not to be documented.
* @endcond
*/
/**
* @brief Basic implementation of a directed adjacency matrix.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Allocator>
class basic_adjacency_matrix {
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, std::size_t>, "Invalid value type");
using container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Vertex type. */
using vertex_type = size_type;
/*! @brief Edge type. */
using edge_type = std::pair<vertex_type, vertex_type>;
/*! @brief Vertex iterator type. */
using vertex_iterator = iota_iterator<vertex_type>;
/*! @brief Edge iterator type. */
using edge_iterator = internal::edge_iterator;
/*! @brief Default constructor. */
basic_adjacency_matrix() noexcept(noexcept(allocator_type{}))
: basic_adjacency_matrix{0u} {}
/**
* @brief Constructs an empty container with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_adjacency_matrix(const allocator_type &allocator) noexcept
: basic_adjacency_matrix{0u, allocator} {}
/**
* @brief Constructs an empty container with a given allocator and user
* supplied number of vertices.
* @param vertices Number of vertices.
* @param allocator The allocator to use.
*/
basic_adjacency_matrix(const size_type vertices, const allocator_type &allocator = allocator_type{})
: matrix(vertices * vertices),
vert{vertices} {}
/**
* @brief Copy constructor.
* @param other The instance to copy from.
*/
basic_adjacency_matrix(const basic_adjacency_matrix &other)
: basic_adjacency_matrix{other, other.get_allocator()} {}
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
basic_adjacency_matrix(const basic_adjacency_matrix &other, const allocator_type &allocator)
: matrix{other.matrix, allocator},
vert{other.vert} {}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_adjacency_matrix(basic_adjacency_matrix &&other) noexcept
: basic_adjacency_matrix{std::move(other), other.get_allocator()} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_adjacency_matrix(basic_adjacency_matrix &&other, const allocator_type &allocator)
: matrix{std::move(other.matrix), allocator},
vert{std::exchange(other.vert, 0u)} {}
/**
* @brief Default copy assignment operator.
* @param other The instance to copy from.
* @return This container.
*/
basic_adjacency_matrix &operator=(const basic_adjacency_matrix &other) {
matrix = other.matrix;
vert = other.vert;
return *this;
}
/**
* @brief Default move assignment operator.
* @param other The instance to move from.
* @return This container.
*/
basic_adjacency_matrix &operator=(basic_adjacency_matrix &&other) noexcept {
matrix = std::move(other.matrix);
vert = std::exchange(other.vert, 0u);
return *this;
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return matrix.get_allocator();
}
/*! @brief Clears the adjacency matrix. */
void clear() noexcept {
matrix.clear();
vert = {};
}
/**
* @brief Exchanges the contents with those of a given adjacency matrix.
* @param other Adjacency matrix to exchange the content with.
*/
void swap(adjacency_matrix &other) {
using std::swap;
swap(matrix, other.matrix);
swap(vert, other.vert);
}
/**
* @brief Returns the number of vertices.
* @return The number of vertices.
*/
[[nodiscard]] size_type size() const noexcept {
return vert;
}
/**
* @brief Returns an iterable object to visit all vertices of a matrix.
* @return An iterable object to visit all vertices of a matrix.
*/
[[nodiscard]] iterable_adaptor<vertex_iterator> vertices() const noexcept {
return {0u, vert};
}
/**
* @brief Returns an iterable object to visit all edges of a matrix.
* @return An iterable object to visit all edges of a matrix.
*/
[[nodiscard]] iterable_adaptor<edge_iterator> edges() const noexcept {
return {{matrix.data(), vert, 0u}, {matrix.data(), vert, matrix.size()}};
}
/**
* @brief Returns an iterable object to visit all edges of a vertex.
* @param vertex The vertex of which to return all edges.
* @return An iterable object to visit all edges of a vertex.
*/
[[nodiscard]] iterable_adaptor<edge_iterator> edges(const vertex_type vertex) const noexcept {
const auto first = vertex * vert;
const auto last = vertex * vert + vert;
return {{matrix.data(), vert, first, last}, {matrix.data(), vert, last, last}};
}
/**
* @brief Resizes an adjacency matrix.
* @param vertices The new number of vertices.
*/
void resize(const size_type vertices) {
matrix.resize(vertices * vertices);
vert = vertices;
}
/**
* @brief Inserts an edge into the adjacency matrix, if it does not exist.
* @param lhs The left hand vertex of the edge.
* @param rhs The right hand vertex of the edge.
* @return A pair consisting of an iterator to the inserted element (or to
* the element that prevented the insertion) and a bool denoting whether the
* insertion took place.
*/
std::pair<edge_iterator, bool> insert(const vertex_type lhs, const vertex_type rhs) {
const auto pos = lhs * vert + rhs;
const auto inserted = !std::exchange(matrix[pos], 1u);
return {edge_iterator{matrix.data(), vert, pos}, inserted};
}
/**
* @brief Removes the edge associated with a pair of given vertices.
* @param lhs The left hand vertex of the edge.
* @param rhs The right hand vertex of the edge.
* @return Number of elements removed (either 0 or 1).
*/
size_type erase(const vertex_type lhs, const vertex_type rhs) {
return std::exchange(matrix[lhs * vert + rhs], 0u);
}
/**
* @brief Checks if an adjacency matrix contains a given edge.
* @param lhs The left hand vertex of the edge.
* @param rhs The right hand vertex of the edge.
* @return True if there is such an edge, false otherwise.
*/
[[nodiscard]] bool contains(const vertex_type lhs, const vertex_type rhs) const {
const auto pos = lhs * vert + rhs;
return pos < matrix.size() && matrix[pos];
}
private:
container_type matrix;
size_type vert;
};
} // namespace entt
#endif

17
src/entt/graph/fwd.hpp Normal file
View File

@@ -0,0 +1,17 @@
#ifndef ENTT_GRAPH_FWD_HPP
#define ENTT_GRAPH_FWD_HPP
#include <cstddef>
#include <memory>
namespace entt {
template<typename = std::allocator<std::size_t>>
struct basic_adjacency_matrix;
/*! @brief Alias declaration for the most common use case. */
using adjacency_matrix = basic_adjacency_matrix<>;
} // namespace entt
#endif

View File

@@ -217,6 +217,10 @@ SETUP_BASIC_TEST(sparse_set entt/entity/sparse_set.cpp)
SETUP_BASIC_TEST(storage entt/entity/storage.cpp)
SETUP_BASIC_TEST(view entt/entity/view.cpp)
# Test graph
SETUP_BASIC_TEST(adjacency_matrix entt/graph/adjacency_matrix.cpp)
# Test locator
SETUP_BASIC_TEST(locator entt/locator/locator.cpp)

View File

@@ -0,0 +1,289 @@
#include <memory>
#include <utility>
#include <gtest/gtest.h>
#include <entt/graph/adjacency_matrix.hpp>
#include "../common/throwing_allocator.hpp"
TEST(AdjacencyMatrix, Resize) {
entt::adjacency_matrix adjacency_matrix{2};
adjacency_matrix.insert(0u, 1u);
ASSERT_EQ(adjacency_matrix.size(), 2u);
ASSERT_TRUE(adjacency_matrix.contains(0u, 1u));
adjacency_matrix.resize(3u);
ASSERT_EQ(adjacency_matrix.size(), 3u);
ASSERT_TRUE(adjacency_matrix.contains(0u, 1u));
}
TEST(AdjacencyMatrix, Constructors) {
entt::adjacency_matrix adjacency_matrix{};
ASSERT_EQ(adjacency_matrix.size(), 0u);
adjacency_matrix = entt::adjacency_matrix{std::allocator<bool>{}};
adjacency_matrix = entt::adjacency_matrix{3u, std::allocator<bool>{}};
ASSERT_EQ(adjacency_matrix.size(), 3u);
adjacency_matrix.insert(0u, 1u);
entt::adjacency_matrix temp{adjacency_matrix, adjacency_matrix.get_allocator()};
entt::adjacency_matrix other{std::move(adjacency_matrix), adjacency_matrix.get_allocator()};
ASSERT_EQ(adjacency_matrix.size(), 0u);
ASSERT_EQ(other.size(), 3u);
ASSERT_FALSE(adjacency_matrix.contains(0u, 1u));
ASSERT_TRUE(other.contains(0u, 1u));
}
TEST(AdjacencyMatrix, Copy) {
entt::adjacency_matrix adjacency_matrix{3u};
adjacency_matrix.insert(0u, 1u);
entt::adjacency_matrix other{adjacency_matrix};
ASSERT_EQ(adjacency_matrix.size(), 3u);
ASSERT_EQ(other.size(), 3u);
ASSERT_TRUE(adjacency_matrix.contains(0u, 1u));
ASSERT_TRUE(other.contains(0u, 1u));
adjacency_matrix.resize(4u);
adjacency_matrix.insert(0u, 2u);
other.insert(1u, 2u);
other = adjacency_matrix;
ASSERT_EQ(other.size(), 4u);
ASSERT_EQ(adjacency_matrix.size(), 4u);
ASSERT_TRUE(other.contains(0u, 1u));
ASSERT_FALSE(other.contains(1u, 2u));
ASSERT_TRUE(other.contains(0u, 2u));
}
TEST(AdjacencyMatrix, Move) {
entt::adjacency_matrix adjacency_matrix{3u};
adjacency_matrix.insert(0u, 1u);
entt::adjacency_matrix other{std::move(adjacency_matrix)};
ASSERT_EQ(adjacency_matrix.size(), 0u);
ASSERT_EQ(other.size(), 3u);
ASSERT_FALSE(adjacency_matrix.contains(0u, 1u));
ASSERT_TRUE(other.contains(0u, 1u));
adjacency_matrix = {};
adjacency_matrix.resize(4u);
adjacency_matrix.insert(0u, 2u);
other.insert(1u, 2u);
other = std::move(adjacency_matrix);
ASSERT_EQ(other.size(), 4u);
ASSERT_EQ(adjacency_matrix.size(), 0u);
ASSERT_FALSE(other.contains(0u, 1u));
ASSERT_FALSE(other.contains(1u, 2u));
ASSERT_TRUE(other.contains(0u, 2u));
}
TEST(AdjacencyMatrix, Swap) {
entt::adjacency_matrix adjacency_matrix{3u};
entt::adjacency_matrix other{};
adjacency_matrix.insert(0u, 1u);
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(adjacency_matrix.size(), 3u);
ASSERT_TRUE(adjacency_matrix.contains(0u, 1u));
ASSERT_FALSE(other.contains(0u, 1u));
adjacency_matrix.swap(other);
ASSERT_EQ(other.size(), 3u);
ASSERT_EQ(adjacency_matrix.size(), 0u);
ASSERT_FALSE(adjacency_matrix.contains(0u, 1u));
ASSERT_TRUE(other.contains(0u, 1u));
}
TEST(AdjacencyMatrix, Insert) {
entt::adjacency_matrix adjacency_matrix{3u};
auto first = adjacency_matrix.insert(0u, 1u);
auto second = adjacency_matrix.insert(0u, 2u);
auto other = adjacency_matrix.insert(0u, 1u);
ASSERT_TRUE(first.second);
ASSERT_TRUE(second.second);
ASSERT_FALSE(other.second);
ASSERT_NE(first.first, second.first);
ASSERT_EQ(first.first, other.first);
ASSERT_EQ(*first.first, std::make_pair(std::size_t{0u}, std::size_t{1u}));
ASSERT_EQ(*second.first, std::make_pair(std::size_t{0u}, std::size_t{2u}));
}
TEST(AdjacencyMatrix, Erase) {
entt::adjacency_matrix adjacency_matrix{3u};
adjacency_matrix.insert(0u, 1u);
ASSERT_TRUE(adjacency_matrix.contains(0u, 1u));
ASSERT_EQ(adjacency_matrix.erase(0u, 1u), 1u);
ASSERT_EQ(adjacency_matrix.erase(0u, 1u), 0u);
ASSERT_FALSE(adjacency_matrix.contains(0u, 1u));
}
TEST(AdjacencyMatrix, Clear) {
entt::adjacency_matrix adjacency_matrix{3u};
adjacency_matrix.insert(0u, 1u);
adjacency_matrix.insert(0u, 2u);
ASSERT_TRUE(adjacency_matrix.contains(0u, 1u));
ASSERT_TRUE(adjacency_matrix.contains(0u, 2u));
ASSERT_EQ(adjacency_matrix.size(), 3u);
adjacency_matrix.clear();
ASSERT_FALSE(adjacency_matrix.contains(0u, 1u));
ASSERT_FALSE(adjacency_matrix.contains(0u, 2u));
ASSERT_EQ(adjacency_matrix.size(), 0u);
}
TEST(AdjacencyMatrix, VertexIterator) {
using iterator = typename entt::adjacency_matrix::vertex_iterator;
static_assert(std::is_same_v<iterator::value_type, std::size_t>);
static_assert(std::is_same_v<iterator::pointer, void>);
static_assert(std::is_same_v<iterator::reference, std::size_t>);
entt::adjacency_matrix adjacency_matrix{2u};
const auto iterable = adjacency_matrix.vertices();
iterator end{iterable.begin()};
iterator begin{};
begin = iterable.end();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.begin());
ASSERT_EQ(end, iterable.end());
ASSERT_NE(begin, end);
ASSERT_EQ(*begin, 0u);
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(*begin, 1u);
ASSERT_EQ(++begin, iterable.end());
}
TEST(AdjacencyMatrix, EdgeIterator) {
using iterator = typename entt::adjacency_matrix::edge_iterator;
static_assert(std::is_same_v<iterator::value_type, std::pair<std::size_t, std::size_t>>);
static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<std::size_t, std::size_t>>>);
static_assert(std::is_same_v<iterator::reference, std::pair<std::size_t, std::size_t>>);
entt::adjacency_matrix adjacency_matrix{3u};
adjacency_matrix.insert(0u, 1u);
adjacency_matrix.insert(0u, 2u);
const auto iterable = adjacency_matrix.edges();
iterator end{iterable.begin()};
iterator begin{};
begin = iterable.end();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.begin());
ASSERT_EQ(end, iterable.end());
ASSERT_NE(begin, end);
ASSERT_EQ(*begin, std::make_pair(std::size_t{0u}, std::size_t{1u}));
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(*begin.operator->(), std::make_pair(std::size_t{0u}, std::size_t{2u}));
ASSERT_EQ(++begin, iterable.end());
}
TEST(AdjacencyMatrix, Vertices) {
entt::adjacency_matrix adjacency_matrix{};
auto iterable = adjacency_matrix.vertices();
ASSERT_EQ(adjacency_matrix.size(), 0u);
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.resize(2u);
iterable = adjacency_matrix.vertices();
ASSERT_EQ(adjacency_matrix.size(), 2u);
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, 0u);
ASSERT_EQ(*it, 1u);
ASSERT_EQ(++it, iterable.end());
}
TEST(AdjacencyMatrix, Edges) {
entt::adjacency_matrix adjacency_matrix{3u};
auto iterable = adjacency_matrix.edges();
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(0u, 1u);
adjacency_matrix.insert(1u, 2u);
iterable = adjacency_matrix.edges();
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
ASSERT_EQ(*it.operator->(), std::make_pair(std::size_t{1u}, std::size_t{2u}));
ASSERT_EQ(++it, iterable.end());
}
TEST(AdjacencyMatrix, EdgesByVertex) {
entt::adjacency_matrix adjacency_matrix{3u};
auto iterable = adjacency_matrix.edges();
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(0u, 1u);
adjacency_matrix.insert(1u, 2u);
iterable = adjacency_matrix.edges(0u);
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
ASSERT_EQ(it, iterable.end());
}
TEST(AdjacencyMatrix, ThrowingAllocator) {
using allocator = test::throwing_allocator<std::size_t>;
using exception = typename allocator::exception_type;
entt::basic_adjacency_matrix<allocator> adjacency_matrix{2u};
adjacency_matrix.insert(0u, 1u);
allocator::trigger_on_allocate = true;
ASSERT_EQ(adjacency_matrix.size(), 2u);
ASSERT_TRUE(adjacency_matrix.contains(0u, 1u));
ASSERT_THROW(adjacency_matrix.resize(4u), exception);
ASSERT_EQ(adjacency_matrix.size(), 2u);
ASSERT_TRUE(adjacency_matrix.contains(0u, 1u));
}