Commit 60b426d6 authored by Gauthier Quesnel's avatar Gauthier Quesnel
Browse files

array: add a data-array optimized for dynamics objects

parent 55d470e2
Pipeline #2150 passed with stage
in 42 seconds
......@@ -24,100 +24,419 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ORG_VLEPROJECT_BITS_DATAARRAY_HPP
#define ORG_VLEPROJECT_BITS_DATAARRAY_HPP
#include <algorithm>
#include <type_traits>
#include <vector>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
namespace bits {
using ID = std::int32_t;
constexpr ID ID_index_mask = 0x0000ffff;
constexpr ID ID_key_mask = 0xffff0000;
constexpr int
get_index(ID id) noexcept
{
return id & ID_index_mask;
}
constexpr int
get_key(ID id) noexcept
{
return id & ID_key_mask;
}
namespace vle {
namespace glvle {
inline constexpr bool
is_valid(ID id) noexcept
{
return get_key(id) != 0;
}
/**
* @brief An optimized fixed size array for dynamics objects.
* @details Handles everything from any trivial, pod or object.
* - linear memory/iteration
* - O(1) alloc/free
* - stable indices
* - weak references
* - zero overhead derefs
*
* @tparam T The type of object the data_array holds.
*/
template<typename T>
struct DataArray<T>
struct data_array
{
struct item
{
T item;
int id; // (key << 16 | index) for alloced entries, (0 | nextFreeIndex)
// for free list entries
ID id; // (key << 16 | index) for alloced entries, (0 | nextFreeIndex)
// for free list entries.
};
// allocs items (max 64k), then Clear()
void init(int count);
using this_type = data_array<T>;
using value_type = T;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = item*;
using const_iterator = const item*;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using size_type = int;
using difference_type = ptrdiff_t;
data_array() = default;
// frees items
void dispose();
~data_array();
// resets data members, (runs destructors* on outstanding items,
// *optional)
/** Allocate a vector of items (max 65536 items).
*
* @return true if success, false if capacity is not in [0..65536[.
*/
bool init(int capacity_);
/** Resets data members, (runs destructors* on outstanding items, *optional
*/
void clear();
T& Alloc(); // alloc (memclear* and/or construct*, *optional) an item from
// freeList or items[maxUsed++], sets id to (nextKey++ << 16) |
// index
void Free(T&); // puts entry on free list (uses id to store next)
int GetID(T&); // accessor to the id part if Item
T& Get(id) // return item[id & 0xFFFF];
T* TryToGet(
id); // validates id, then returns item, returns null if invalid. for
// cases like AI references and others where 'the thing might have
// been deleted out from under me'
bool Next(T*&); // return next item where id & 0xFFFF0000 != 0 (ie items
// not on free list)
std::vector<item> items;
Item* items;
int maxSize; // total size
int maxUsed; // highest index ever alloced
int count; // num alloced items
int nextKey; // [1..2^16] (don't let == 0)
int freeHead; // index of first free entry
// alloc (memclear* and/or construct*, *optional) an item from freeList or
// items[max_used++], sets id to (next_key++ << 16) | index
T& alloc();
// puts entry on free list (uses id to store next)
void free(T&);
// accessor to the id part if Item
int get_id(T&);
T& get(ID id); // return item[id & 0xFFFF];
/**
* @brief Get a T from an ID.
*
* @details Validates ID, then returns item, returns null if invalid. For
* cases like AI references and others where 'the thing might have been
* deleted out from under me.
*/
T* try_to_get(ID id);
/**
* @brief Return next item where id & 0xFFFF0000 != 0 (ie items not on free
* list).
*
* @param [description]
* @return [description]
*/
bool next(T*&);
iterator begin() noexcept;
const_iterator begin() const noexcept;
const_iterator cbegin() const noexcept;
iterator end() noexcept;
const_iterator end() const noexcept;
const_iterator cend() const noexcept;
reverse_iterator rbegin() noexcept;
const_reverse_iterator rbegin() const noexcept;
const_reverse_iterator crbegin() const noexcept;
reverse_iterator rend() noexcept;
const_reverse_iterator rend() const noexcept;
const_reverse_iterator crend() const noexcept;
bool full() const noexcept;
item* items = nullptr; // items vector.
int max_size = 0; // total size
int max_used = 0; // highest index ever alloced
int capacity = 0; // num alloced items
int next_key = 1; // [1..2^16] (don't let == 0)
int free_head = -1; // index of first free entry
};
template<typename T>
data_array<T>::~data_array()
{
if (items)
::free(items);
}
template<typename T>
bool
data_array<T>::init(int capacity_)
{
clear();
if (capacity_ < 0 || capacity_ > ID_index_mask)
return false;
items = (data_array<T>::item*)::malloc(static_cast<size_t>(capacity) *
sizeof(item));
max_size = 0;
max_used = 0;
capacity = capacity_;
next_key = 1;
free_head = -1;
return true;
}
template<typename T>
void
DataArray<T>::init(int count)
Do_clear(typename data_array<T>::item* items, const int size, std::true_type)
{
items.resize(static_cast<size_t>(count));
std::memset(items, 0, sizeof(typename data_array<T>::item) * size);
}
template<typename T>
void
DataArray<T>::dispose()
Do_clear(typename data_array<T>::item* items, const int size, std::false_type)
{
items.resize(static_cast<size_t>(0));
items.shrink_to_fit();
for (int i = 0; i != size; ++i) {
if (is_valid(items[i].id)) {
items[i].item.~T();
items[i].id = 0;
}
}
}
template<typename T, true>
template<typename T>
void
Do_clear(std::vector<T>& items)
{}
data_array<T>::clear()
{
Do_clear<T>(items, max_size, std::is_trivial<T>());
items = nullptr;
max_size = 0;
max_used = 0;
capacity = 0;
next_key = 1;
free_head = -1;
}
template<typename T, false>
template<typename T>
void
Do_clear(std::vector<T>& items)
Do_alloc(T& /*t*/, std::true_type)
{
std::for_each(
std::begin(items), std::end(items), [](auto& elem) { elem.~T(); });
// std::memset(&t, 0, sizeof(T));
}
template<typename T>
void
DataArray<T>::clear()
Do_alloc(T& t, std::false_type)
{
t.T::T();
}
template<typename T>
T&
data_array<T>::alloc()
{
Do_clear<T, std::is_trivial<T>::value>(items);
int new_index;
if (free_head >= 0) {
new_index = free_head;
if (is_valid(items[free_head].id))
free_head = -1;
else
free_head = get_index(items[free_head].id);
} else {
new_index = max_used++;
}
Do_alloc<T>(items[new_index].item, std::is_trivial<T>());
if (next_key == ID_key_mask)
next_key = 1;
items[new_index].id = (next_key++ << 16) | new_index;
++max_size;
return items[new_index].item;
}
template<typename T>
void
Do_free(T& /*t*/, std::true_type)
{
// std::memset(&t, 0, sizeof(T));
}
template<typename T>
void
Do_free(T& t, std::false_type)
{
t.~T();
}
template<typename T>
void
data_array<T>::free(T& t)
{
auto id = get_id(t);
auto index = get_index(id);
assert(&items[index] == static_cast<void*>(&t));
assert(items[index].id == id);
assert(is_valid(id));
Do_free<T>(items[index].item, std::is_trivial<T>());
items[index].id = free_head;
free_head = index;
--max_size;
}
template<typename T>
T&
DataArray<T>::alloc();
{}
data_array<T>::get(ID id)
{
return items[get_index(id)];
}
template<typename T>
int
data_array<T>::get_id(T& t)
{
auto offset = offsetof(data_array<T>::item, id);
auto* ptr = reinterpret_cast<char*>(&t);
ptr += offset;
return static_cast<int>(*reinterpret_cast<int*>(ptr));
}
template<typename T>
T*
data_array<T>::try_to_get(ID id)
{
if (get_key(id)) {
auto index = get_index(id);
return &items[index].item;
}
return nullptr;
}
template<typename T>
bool
data_array<T>::next(T*& t)
{
if (t) {
int id = get_id(*t);
int index = get_index(id);
++index;
for (; index < max_used; ++index) {
if (is_valid(items[index].id)) {
t = &items[index].item;
return true;
}
}
}
return false;
}
template<typename T>
inline typename data_array<T>::iterator
data_array<T>::begin() noexcept
{
return items;
}
template<typename T>
inline typename data_array<T>::const_iterator
data_array<T>::begin() const noexcept
{
return items;
}
template<typename T>
inline typename data_array<T>::const_iterator
data_array<T>::cbegin() const noexcept
{
return items;
}
template<typename T>
inline typename data_array<T>::iterator
data_array<T>::end() noexcept
{
return items + max_used;
}
// alloc (memclear* and/or construct*, *optional) an item from
// freeList or items[maxUsed++], sets id to (nextKey++ << 16) |
// index
template<typename T>
inline typename data_array<T>::const_iterator
data_array<T>::end() const noexcept
{
return items + max_used;
}
template<typename T>
inline typename data_array<T>::const_iterator
data_array<T>::cend() const noexcept
{
return items + max_used;
}
template<typename T>
inline typename data_array<T>::reverse_iterator
data_array<T>::rbegin() noexcept
{
return reverse_iterator(items + max_used);
}
template<typename T>
inline typename data_array<T>::const_reverse_iterator
data_array<T>::rbegin() const noexcept
{
return const_reverse_iterator(items + max_used);
}
template<typename T>
inline typename data_array<T>::const_reverse_iterator
data_array<T>::crbegin() const noexcept
{
return const_reverse_iterator(items + max_used);
}
template<typename T>
inline typename data_array<T>::reverse_iterator
data_array<T>::rend() noexcept
{
return reverse_iterator(items);
}
template<typename T>
inline typename data_array<T>::const_reverse_iterator
data_array<T>::rend() const noexcept
{
return const_reverse_iterator(items);
}
template<typename T>
inline typename data_array<T>::const_reverse_iterator
data_array<T>::crend() const noexcept
{
return const_reverse_iterator(items);
}
template<typename T>
bool
data_array<T>::full() const noexcept
{
return free_head == -1 && max_used == capacity;
}
} // glvle
} // vle
} // bits
#endif // ORG_VLEPROJECT_BITS_DATAARRAY_HPP
......@@ -20,6 +20,7 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <bits/array.hpp>
#include <bits/fixed-2darray.hpp>
#include <bits/fixed-array.hpp>
#include <bits/unit-test.hpp>
......@@ -27,6 +28,129 @@
#include <functional>
#include <numeric>
static void
check_array()
{
struct position
{
float x;
};
bits::data_array<position> array;
Ensures(array.items == nullptr);
Ensures(array.max_size == 0);
Ensures(array.max_used == 0);
Ensures(array.capacity == 0);
Ensures(array.next_key == 1);
Ensures(array.free_head == -1);
bool is_init = array.init(3);
Ensures(array.items != nullptr);
Ensures(array.max_size == 0);
Ensures(array.max_used == 0);
Ensures(array.capacity == 3);
Ensures(array.next_key == 1);
Ensures(array.free_head == -1);
Ensures(is_init);
{
auto& first = array.alloc();
first.x = 0.f;
Ensures(array.max_size == 1);
Ensures(array.max_used == 1);
Ensures(array.capacity == 3);
Ensures(array.next_key == 2);
Ensures(array.free_head == -1);
auto& second = array.alloc();
Ensures(array.max_size == 2);
Ensures(array.max_used == 2);
Ensures(array.capacity == 3);
Ensures(array.next_key == 3);
Ensures(array.free_head == -1);
second.x = 1.f;
auto& third = array.alloc();
Ensures(array.max_size == 3);
Ensures(array.max_used == 3);
Ensures(array.capacity == 3);
Ensures(array.next_key == 4);
Ensures(array.free_head == -1);
third.x = 2.f;
Ensures(array.full());
}
array.clear();
Ensures(array.items == nullptr);
Ensures(array.max_size == 0);
Ensures(array.max_used == 0);
Ensures(array.capacity == 0);
Ensures(array.next_key == 1);
Ensures(array.free_head == -1);
is_init = array.init(3);
{
auto& d1 = array.alloc();
d1.x = 1.f;
auto& d2 = array.alloc();
d2.x = 2.f;
auto& d3 = array.alloc();
d3.x = 3.f;
Ensures(array.items);
Ensures(array.max_size == 3);
Ensures(array.max_used == 3);
Ensures(array.capacity == 3);
Ensures(array.next_key == 4);
Ensures(array.free_head == -1);
array.free(d1);
Ensures(array.items);
Ensures(array.max_size == 2);
Ensures(array.max_used == 3);
Ensures(array.capacity == 3);
Ensures(array.next_key == 4);
Ensures(array.free_head == 0);
array.free(d2);
Ensures(array.items);
Ensures(array.max_size == 1);
Ensures(array.max_used == 3);
Ensures(array.capacity == 3);
Ensures(array.next_key == 4);
Ensures(array.free_head == 1);
array.free(d3);
Ensures(array.items);
Ensures(array.max_size == 0);
Ensures(array.max_used == 3);
Ensures(array.capacity == 3);
Ensures(array.next_key == 4);
Ensures(array.free_head == 2);
auto& n1 = array.alloc();
auto& n2 = array.alloc();
auto& n3 = array.alloc();
Ensures(bits::get_index(array.get_id(n1)) == 2);
Ensures(bits::get_index(array.get_id(n2)) == 1);
Ensures(bits::get_index(array.get_id(n3)) == 0);
Ensures(array.items);
Ensures(array.max_size == 3);
Ensures(array.max_used == 3);
Ensures(array.capacity == 3);
Ensures(array.next_key == 7);
Ensures(array.free_head == -1);
}
}
static void
check_fixed_array()
{
......@@ -133,6 +257,7 @@ check_fixed_2darray()
int
main(int /* argc */, char* /* argv */ [])
{
check_array();
check_fixed_array();
check_fixed_2darray();
......