sparse indexer for sparse_set and sparse_map

This commit is contained in:
2018-12-28 08:03:02 +07:00
parent 0530da460c
commit fc1b59cee2
2 changed files with 139 additions and 41 deletions

124
ecs.hpp
View File

@@ -159,6 +159,33 @@ namespace ecs_hpp
}
}
// -----------------------------------------------------------------------------
//
// detail::sparse_indexer
//
// -----------------------------------------------------------------------------
namespace ecs_hpp
{
namespace detail
{
template < typename T
, bool = std::is_unsigned<T>::value && sizeof(T) <= sizeof(std::size_t) >
struct sparse_unsigned_indexer {
std::size_t operator()(const T v) const noexcept {
return static_cast<std::size_t>(v);
}
};
template < typename T >
struct sparse_unsigned_indexer<T, false> {};
template < typename T >
struct sparse_indexer
: public sparse_unsigned_indexer<T> {};
}
}
// -----------------------------------------------------------------------------
//
// detail::sparse_set
@@ -169,12 +196,10 @@ namespace ecs_hpp
{
namespace detail
{
template < typename T >
template < typename T
, typename Indexer = sparse_indexer<T> >
class sparse_set final {
public:
static_assert(
std::is_unsigned<T>::value,
"sparse_set<T> can contain an unsigned integers only");
using iterator = typename std::vector<T>::iterator;
using const_iterator = typename std::vector<T>::const_iterator;
public:
@@ -205,27 +230,42 @@ namespace ecs_hpp
return cbegin() + static_cast<dt>(size_);
}
public:
bool insert(const T v) {
sparse_set(const Indexer& indexer = Indexer())
: indexer_(indexer) {}
bool insert(T&& v) {
if ( has(v) ) {
return false;
}
if ( v >= capacity_ ) {
reserve(new_capacity_for_(v + 1u));
const std::size_t vi = indexer_(v);
if ( vi >= capacity_ ) {
reserve(new_capacity_for_(vi + 1u));
}
dense_[size_] = v;
sparse_[v] = size_;
dense_[size_] = std::move(v);
sparse_[vi] = size_;
++size_;
return true;
}
bool unordered_erase(const T v) noexcept {
bool insert(const T& v) {
return insert(T(v));
}
template < typename... Args >
bool emplace(Args&&... args) {
return insert(T(std::forward<Args>(args)...));
}
bool unordered_erase(const T& v)
noexcept(std::is_nothrow_move_assignable<T>::value)
{
if ( !has(v) ) {
return false;
}
const std::size_t index = sparse_[v];
const T last = dense_[size_ - 1u];
dense_[index] = last;
sparse_[last] = index;
const std::size_t vi = indexer_(v);
const std::size_t index = sparse_[vi];
dense_[index] = std::move(dense_[size_ - 1u]);
sparse_[indexer_(dense_[index])] = index;
--size_;
return true;
}
@@ -234,29 +274,31 @@ namespace ecs_hpp
size_ = 0u;
}
bool has(const T v) const noexcept {
return v < capacity_
&& sparse_[v] < size_
&& dense_[sparse_[v]] == v;
bool has(const T& v) const noexcept {
const std::size_t vi = indexer_(v);
return vi < capacity_
&& sparse_[vi] < size_
&& dense_[sparse_[vi]] == v;
}
const_iterator find(const T v) const noexcept {
const_iterator find(const T& v) const noexcept {
const std::size_t vi = indexer_(v);
return has(v)
? begin() + sparse_[v]
? begin() + sparse_[vi]
: end();
}
std::size_t get_index(const T v) const {
std::size_t get_index(const T& v) const {
const auto p = find_index(v);
if ( p.second ) {
return p.first;
}
throw std::out_of_range("sparse_set<T>");
throw std::out_of_range("sparse_set");
}
std::pair<std::size_t,bool> find_index(const T v) const noexcept {
std::pair<std::size_t,bool> find_index(const T& v) const noexcept {
return has(v)
? std::make_pair(sparse_[v], true)
? std::make_pair(sparse_[indexer_(v)], true)
: std::make_pair(std::size_t(-1), false);
}
@@ -299,6 +341,7 @@ namespace ecs_hpp
return std::max(capacity_ * 2u, nsize);
}
private:
Indexer indexer_;
std::vector<T> dense_;
std::vector<std::size_t> sparse_;
std::size_t size_{0u};
@@ -317,12 +360,11 @@ namespace ecs_hpp
{
namespace detail
{
template < typename K, typename T >
template < typename K
, typename T
, typename Indexer = sparse_indexer<K> >
class sparse_map final {
public:
static_assert(
std::is_unsigned<K>::value,
"sparse_map<K,T> can contain unsigned integers keys only");
using iterator = typename std::vector<K>::iterator;
using const_iterator = typename std::vector<K>::const_iterator;
public:
@@ -350,7 +392,10 @@ namespace ecs_hpp
return keys_.cend();
}
public:
bool insert(const K k, const T& v) {
sparse_map(const Indexer& indexer = Indexer())
: keys_(indexer) {}
bool insert(const K& k, const T& v) {
if ( keys_.has(k) ) {
return false;
}
@@ -363,7 +408,7 @@ namespace ecs_hpp
}
}
bool insert(const K k, T&& v) {
bool insert(const K& k, T&& v) {
if ( keys_.has(k) ) {
return false;
}
@@ -377,7 +422,7 @@ namespace ecs_hpp
}
template < typename... Args >
bool emplace(const K k, Args&&... args) {
bool emplace(const K& k, Args&&... args) {
if ( keys_.has(k) ) {
return false;
}
@@ -390,7 +435,10 @@ namespace ecs_hpp
}
}
bool unordered_erase(const K k) {
std::enable_if_t<
std::is_nothrow_move_assignable<K>::value,
bool>
unordered_erase(const K& k) {
if ( !keys_.has(k) ) {
return false;
}
@@ -406,26 +454,26 @@ namespace ecs_hpp
values_.clear();
}
bool has(const K k) const noexcept {
bool has(const K& k) const noexcept {
return keys_.has(k);
}
T& get_value(const K k) {
T& get_value(const K& k) {
return values_[keys_.get_index(k)];
}
const T& get_value(const K k) const {
const T& get_value(const K& k) const {
return values_[keys_.get_index(k)];
}
T* find_value(const K k) noexcept {
T* find_value(const K& k) noexcept {
const auto ip = keys_.find_index(k);
return ip.second
? &values_[ip.first]
: nullptr;
}
const T* find_value(const K k) const noexcept {
const T* find_value(const K& k) const noexcept {
const auto ip = keys_.find_index(k);
return ip.second
? &values_[ip.first]
@@ -453,7 +501,7 @@ namespace ecs_hpp
return values_.capacity();
}
private:
sparse_set<K> keys_;
sparse_set<K, Indexer> keys_;
std::vector<T> values_;
};
}

View File

@@ -37,6 +37,19 @@ namespace
return l.x == r.x
&& l.y == r.y;
}
struct mult_indexer {
template < typename T >
std::size_t operator()(const T& v) const noexcept {
return static_cast<std::size_t>(v * 2);
}
};
struct position_c_indexer {
std::size_t operator()(const position_c& v) const noexcept {
return static_cast<std::size_t>(v.x);
}
};
}
TEST_CASE("detail") {
@@ -70,7 +83,7 @@ TEST_CASE("detail") {
SECTION("sparse_set") {
using namespace ecs::detail;
{
sparse_set<unsigned> s;
sparse_set<unsigned, mult_indexer> s{mult_indexer{}};
REQUIRE(s.empty());
REQUIRE_FALSE(s.size());
@@ -84,7 +97,7 @@ TEST_CASE("detail") {
REQUIRE_FALSE(s.empty());
REQUIRE(s.size() == 1u);
REQUIRE(s.capacity() == 43u);
REQUIRE(s.capacity() == 85u);
REQUIRE(s.has(42u));
REQUIRE_FALSE(s.has(84u));
@@ -108,7 +121,7 @@ TEST_CASE("detail") {
REQUIRE_FALSE(s.has(84u));
REQUIRE(s.empty());
REQUIRE_FALSE(s.size());
REQUIRE(s.capacity() == 43u * 2);
REQUIRE(s.capacity() == 85u * 2);
s.insert(42u);
s.insert(84u);
@@ -133,6 +146,22 @@ TEST_CASE("detail") {
REQUIRE_THROWS(s.get_index(42u));
REQUIRE(s.get_index(84u) == 0u);
}
{
sparse_set<position_c, position_c_indexer> s{position_c_indexer()};
REQUIRE(s.insert(position_c(1,2)));
REQUIRE_FALSE(s.insert(position_c(1,2)));
REQUIRE(s.has(position_c(1,2)));
REQUIRE(s.emplace(3,4));
REQUIRE(s.has(position_c(3,4)));
REQUIRE(s.get_index(position_c(1,2)) == 0);
REQUIRE(s.get_index(position_c(3,4)) == 1);
REQUIRE(s.find_index(position_c(1,2)).first == 0);
REQUIRE(s.find_index(position_c(3,4)).first == 1);
REQUIRE(s.find_index(position_c(1,2)).second);
REQUIRE(s.find_index(position_c(3,4)).second);
REQUIRE(s.unordered_erase(position_c(1,2)));
REQUIRE(s.get_index(position_c(3,4)) == 0);
}
}
SECTION("sparse_map") {
using namespace ecs::detail;
@@ -210,6 +239,27 @@ TEST_CASE("detail") {
REQUIRE_FALSE(m.has(42u));
REQUIRE_FALSE(m.has(84u));
}
{
struct obj_t {
int x;
obj_t(int nx) : x(nx) {}
};
sparse_map<position_c, obj_t, position_c_indexer> s{position_c_indexer()};
REQUIRE(s.insert(position_c(1,2), obj_t{1}));
REQUIRE_FALSE(s.insert(position_c(1,2), obj_t{1}));
REQUIRE(s.has(position_c(1,2)));
REQUIRE(s.emplace(position_c(3,4), obj_t{3}));
REQUIRE(s.has(position_c(3,4)));
REQUIRE(s.get_value(position_c(1,2)).x == 1);
REQUIRE(s.get_value(position_c(3,4)).x == 3);
REQUIRE(s.find_value(position_c(1,2))->x == 1);
REQUIRE(s.find_value(position_c(3,4))->x == 3);
REQUIRE(s.find_value(position_c(1,2)));
REQUIRE(s.find_value(position_c(3,4)));
REQUIRE(s.unordered_erase(position_c(1,2)));
REQUIRE(s.get_value(position_c(3,4)).x == 3);
}
}
}