From e20c0e6a8e592cf94dae268af325093c4fe92636 Mon Sep 17 00:00:00 2001 From: Matthew Fioravante Date: Tue, 18 Aug 2020 18:55:50 -0400 Subject: [PATCH] Add DBArray --- CMakeLists.txt | 4 +- Makefile.am | 4 +- src/dbarray.cpp | 91 ++++++++++++++++++ src/dbstring.cpp | 74 --------------- src/lcf/dbarray.h | 191 ++++++++++++++++++++++++++++++++++++++ src/lcf/dbarrayalloc.h | 46 ++++++++++ src/lcf/dbstring.h | 45 +++++---- tests/dbarray.cpp | 204 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 563 insertions(+), 96 deletions(-) create mode 100644 src/dbarray.cpp delete mode 100644 src/dbstring.cpp create mode 100644 src/lcf/dbarray.h create mode 100644 src/lcf/dbarrayalloc.h create mode 100644 tests/dbarray.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1961cbf02..f9c329a34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,7 @@ add_library(lcf) set(LCF_SOURCES src/data.cpp - src/dbstring.cpp + src/dbarray.cpp src/encoder.cpp src/ini.cpp src/inireader.cpp @@ -195,6 +195,8 @@ set(LCF_SOURCES set(LCF_HEADERS src/lcf/data.h + src/lcf/dbarray.h + src/lcf/dbarrayalloc.h src/lcf/dbstring.h src/lcf/encoder.h src/lcf/enum_tags.h diff --git a/Makefile.am b/Makefile.am index 008f2280f..b310a0c7b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -43,7 +43,7 @@ liblcf_la_LDFLAGS = \ -no-undefined liblcf_la_SOURCES = \ src/data.cpp \ - src/dbstring.cpp \ + src/dbarray.cpp \ src/encoder.cpp \ src/ini.cpp \ src/inireader.cpp \ @@ -202,6 +202,8 @@ liblcf_la_SOURCES = \ lcfinclude_HEADERS = \ src/lcf/data.h \ + src/lcf/dbarray.h \ + src/lcf/dbarrayalloc.h \ src/lcf/dbstring.h \ src/lcf/encoder.h \ src/lcf/enum_tags.h \ diff --git a/src/dbarray.cpp b/src/dbarray.cpp new file mode 100644 index 000000000..4b2362c38 --- /dev/null +++ b/src/dbarray.cpp @@ -0,0 +1,91 @@ +#include "lcf/dbarrayalloc.h" +#include "lcf/dbarray.h" +#include "lcf/dbstring.h" +#include +#include + +//#define LCF_DEBUG_DBARRAY + +#ifdef LCF_DEBUG_DBARRAY +#include +#endif + +namespace lcf { + +constexpr DBArrayAlloc::size_type DBArrayAlloc::_empty_buf; +constexpr DBString::size_type DBString::npos; + +static ptrdiff_t HeaderSize(size_t align) { + return std::max(sizeof(DBArrayAlloc::size_type), align); +} + +static size_t AllocSize(size_t size, size_t align) { + return HeaderSize(align) + size; +} + +static void* Adjust(void* p, ptrdiff_t off) { + return reinterpret_cast(reinterpret_cast(p) + off); +} + +void* DBArrayAlloc::alloc(size_type size, size_type field_size, size_type align) { + if (field_size == 0) { + return empty_buf(); + } + assert(align <= alignof(std::max_align_t)); + auto* raw = ::operator new(AllocSize(size, align)); + auto* p = Adjust(raw, HeaderSize(align)); + *get_size_ptr(p) = field_size; +#ifdef LCF_DEBUG_DBARRAY + std::cout << "DBArray: Allocated" + << " size=" << size + << " field_size=" << *get_size_ptr(p) + << " align=" << align + << " ptr=" << raw + << " adjusted=" << p + << std::endl; +#endif + return p; +} + +void DBArrayAlloc::free(void* p, size_type align) noexcept { + assert(p != nullptr); + if (p != empty_buf()) { + auto* raw = Adjust(p, -HeaderSize(align)); +#ifdef LCF_DEBUG_DBARRAY + std::cout << "DBArray: Free" + << " align=" << align + << " ptr=" << raw + << " adjusted=" << p + << " field_size=" << *get_size_ptr(p) + << std::endl; +#endif + ::operator delete(raw); + } +} + +char* DBString::construct_z(const char* s, size_t len) { + auto* p = alloc(len); + if (len) { + std::memcpy(p, s, len + 1); + } + return p; +} + +char* DBString::construct_sv(const char* s, size_t len) { + auto* p = alloc(len); + if (len) { + std::memcpy(p, s, len); + p[len] = '\0'; + } + return p; +} + +DBString& DBString::operator=(const DBString& o) { + if (this != &o) { + destroy(); + _storage = construct_z(o.data(), o.size()); + } + return *this; +} + +} // namespace lcf diff --git a/src/dbstring.cpp b/src/dbstring.cpp deleted file mode 100644 index f4ce22dfe..000000000 --- a/src/dbstring.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "lcf/dbstring.h" -#include - -namespace lcf { - -constexpr DBString::size_type DBString::npos; -alignas(DBString::size_type) constexpr char DBString::_empty_str[sizeof(size_type)]; - -struct DBStringData { - using size_type = DBString::size_type; - - size_type size; - - const char* data() const { - return reinterpret_cast(this + 1); - } - - char* data() { - return reinterpret_cast(this + 1); - } - - static size_type alloc_size(StringView str) { - return sizeof(DBStringData) + str.size() + 1; - } - - static DBStringData* from_data(char* s) { - return reinterpret_cast(s) - 1; - } -}; - -static char* Alloc(StringView str) { - if (str.empty()) { - return DBString::empty_str(); - } - - auto* raw = ::operator new(DBStringData::alloc_size(str)); - auto* db = new (raw) DBStringData(); - db->size = str.size(); - std::memcpy(db->data(), str.data(), db->size); - db->data()[db->size] = '\0'; - - return db->data(); -} - -static void Free(char* str) { - if (str == DBString::empty_str()) { - return; - } - auto* db = DBStringData::from_data(str); - - db->~DBStringData(); - ::operator delete(db); -} - -DBString::DBString(StringView s) - : _storage(Alloc(s)) -{ -} - -DBString& DBString::operator=(const DBString& o) { - if (this != &o) { - // What is strings are the same, skip double lookup? - _reset(); - _storage = Alloc(StringView(o)); - } - return *this; -} - -void DBString::_reset() noexcept { - assert(_storage != nullptr); - Free(_storage); -} - -} // namespace lcf diff --git a/src/lcf/dbarray.h b/src/lcf/dbarray.h new file mode 100644 index 000000000..6415c510f --- /dev/null +++ b/src/lcf/dbarray.h @@ -0,0 +1,191 @@ +/* + * This file is part of liblcf. Copyright (c) 2020 liblcf authors. + * https://github.com/EasyRPG/liblcf - https://easyrpg.org + * + * liblcf is Free/Libre Open Source Software, released under the MIT License. + * For the full copyright and license information, please view the COPYING + * file that was distributed with this source code. + */ + +#ifndef LCF_DBARRAY_H +#define LCF_DBARRAY_H +#include +#include +#include +#include +#include +#include +#include + +#include "lcf/dbarrayalloc.h" +#include "lcf/scope_guard.h" + +namespace lcf { + +// An array data structure optimized for database storage. +// Low memory footprint and not dynamically resizable. +template +class DBArray { + public: + using value_type = T; + using size_type = DBArrayAlloc::size_type; + + using iterator = T*; + using const_iterator = const T*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + constexpr DBArray() = default; + explicit DBArray(size_type count) { + construct(count, [](void* p) { new (p) T(); }); + } + + explicit DBArray(size_type count, const T& value) { + construct(count, [&](void* p) { new (p) T(value); }); + } + + template ::iterator_category>::value, int>::type = 0 + > + DBArray(Iter first, Iter last) { + construct(std::distance(first, last), [&](void* p) { new (p) T(*first); ++first; }); + } + + DBArray(std::initializer_list ilist) + : DBArray(ilist.begin(), ilist.end()) {} + + DBArray(const DBArray& o) : DBArray(o.begin(), o.end()) {} + DBArray(DBArray&& o) noexcept { swap(o); } + + DBArray& operator=(const DBArray&); + DBArray& operator=(DBArray&& o) noexcept; + + void swap(DBArray& o) noexcept { + std::swap(_storage, o._storage); + } + + ~DBArray() { destroy(); } + + T& operator[](size_type i) { return data()[i]; } + const T& operator[](size_type i) const { return data()[i]; } + + T& front() { return (*this)[0]; } + const T& front() const { return (*this)[0]; } + + T& back() { return (*this)[size()-1]; } + const T& back() const { return (*this)[size()-1]; } + + T* data() { return reinterpret_cast(_storage); } + const T* data() const { return reinterpret_cast(_storage); } + + iterator begin() { return data(); } + iterator end() { return data() + size(); } + + const_iterator begin() const { return data(); } + const_iterator end() const { return data() + size(); } + + const_iterator cbegin() const { return begin(); } + const_iterator cend() const { return end(); } + + reverse_iterator rbegin() { return reverse_iterator(end()); } + reverse_iterator rend() { return reverse_iterator(begin()); } + + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + + const_reverse_iterator crbegin() const { return rbegin(); } + const_reverse_iterator crend() const { return rend(); } + + bool empty() const { return size() == 0; } + size_type size() const { return *DBArrayAlloc::get_size_ptr(_storage); } + + private: + T* alloc(size_t count) { + return reinterpret_cast(DBArrayAlloc::alloc(count * sizeof(T), count, alignof(T))); + } + + void free(void* p) { + DBArrayAlloc::free(p, alignof(T)); + } + + template + void construct(size_type count, const F& make); + + void destroy() noexcept; + void destroy_impl(size_type n) noexcept; + private: + void* _storage = DBArrayAlloc::empty_buf(); +}; + +template +inline bool operator==(const DBArray& l, const DBArray& r) { return std::equal(l.begin(), l.end(), r.begin(), r.end()); } +template +inline bool operator!=(const DBArray& l, const DBArray& r) { return !(l == r); } +template +inline bool operator<(const DBArray& l, const DBArray& r) { return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); } +template +inline bool operator>(const DBArray& l, const DBArray& r) { return r < l; } +template +inline bool operator<=(const DBArray& l, const DBArray& r) { return !(l > r); } +template +inline bool operator>=(const DBArray& l, const DBArray& r) { return !(l < r); } + +} // namespace lcf + +namespace lcf { + +template +inline DBArray& DBArray::operator=(const DBArray& o) { + if (this != &o) { + destroy(); + if (!o.empty()) { + auto iter = o.begin(); + construct(o.size(), [&](void* p) { new (p) T(*(iter++)); }); + } + } + return *this; +} + +template +inline DBArray& DBArray::operator=(DBArray&& o) noexcept { + if (this != &o) { + destroy(); + swap(o); + } + return *this; +} + +template +template +void DBArray::construct(size_type count, const F& make) { + auto* p = alloc(count); + + size_type i = 0; + auto rollback = makeScopeGuard([&]() { destroy_impl(i); }); + for (size_type i = 0; i < count; ++i) { + make(p + i); + } + rollback.Dismiss(); + _storage = p; +} + +template +void DBArray::destroy() noexcept { + if (_storage != DBArrayAlloc::empty_buf()) { + destroy_impl(size()); + _storage = DBArrayAlloc::empty_buf(); + } +} + +template +void DBArray::destroy_impl(size_type n) noexcept { + while (n > 0) { + data()[--n].~T(); + } + free(_storage); +} + +} // namespace lcf + +#endif diff --git a/src/lcf/dbarrayalloc.h b/src/lcf/dbarrayalloc.h new file mode 100644 index 000000000..6cb6e8709 --- /dev/null +++ b/src/lcf/dbarrayalloc.h @@ -0,0 +1,46 @@ +/* + * This file is part of liblcf. Copyright (c) 2020 liblcf authors. + * https://github.com/EasyRPG/liblcf - https://easyrpg.org + * + * liblcf is Free/Libre Open Source Software, released under the MIT License. + * For the full copyright and license information, please view the COPYING + * file that was distributed with this source code. + */ + +#ifndef LCF_DBARRAYALLOC_H +#define LCF_DBARRAYALLOC_H +#include +#include +#include +#include +#include +#include +#include + +namespace lcf { + +struct DBArrayAlloc { + using size_type = uint32_t; + + static void* alloc(size_type size, size_type field_size, size_type align); + static void free(void* p, size_type align) noexcept; + + static constexpr void* empty_buf() { + return const_cast(&_empty_buf + 1); + } + + static constexpr size_type* get_size_ptr(void* p) { + return static_cast(p) - 1; + } + + static constexpr const size_type* get_size_ptr(const void* p) { + return static_cast(p) - 1; + } + + private: + static constexpr const size_type _empty_buf = 0; +}; + +} // namespace lcf + +#endif diff --git a/src/lcf/dbstring.h b/src/lcf/dbstring.h index d01c8ce45..686aeb3cd 100644 --- a/src/lcf/dbstring.h +++ b/src/lcf/dbstring.h @@ -19,6 +19,7 @@ #include #include "lcf/string_view.h" +#include "lcf/dbarrayalloc.h" namespace lcf { @@ -41,14 +42,14 @@ class DBString { static constexpr size_type npos = size_type(-1); constexpr DBString() = default; - explicit DBString(StringView s); - explicit DBString(const std::string& s) : DBString(StringView(s)) {} + explicit DBString(StringView s) : _storage(construct_sv(s.data(), s.size())) {} + explicit DBString(const std::string& s) : _storage(construct_z(s.c_str(), s.size())) {} // Explicit construct for general const char* explicit DBString(const char* s) : DBString(StringView(s)) {} // Implicit constructor to capture string literals template - DBString(const char(&literal)[N]) : DBString(StringView(literal)) {} + DBString(const char(&literal)[N]) : _storage(construct_z(literal, N - 1)) {} DBString(const char* s, size_t len) : DBString(StringView(s, len)) {} DBString(const DBString& o) : DBString(StringView(o)) {} @@ -61,7 +62,7 @@ class DBString { std::swap(_storage, o._storage); } - ~DBString() { _reset(); } + ~DBString() { destroy(); } explicit operator std::string() const { return std::string(data(), size()); } operator StringView() const { return StringView(data(), size()); } @@ -75,8 +76,8 @@ class DBString { char& back() { return (*this)[size()-1]; } char back() const { return (*this)[size()-1]; } - char* data() { return _storage; } - const char* data() const { return _storage; } + char* data() { return static_cast(_storage); } + const char* data() const { return static_cast(_storage); } const char* c_str() const { return data(); } @@ -99,16 +100,20 @@ class DBString { const_reverse_iterator crend() const { return rend(); } bool empty() const { return size() == 0; } - size_type size() const; + size_type size() const { return *DBArrayAlloc::get_size_ptr(_storage); } - static constexpr char* empty_str() { - return const_cast(_empty_str + sizeof(size_type)); - } private: - void _reset() noexcept; + char* alloc(size_t count) { + return reinterpret_cast(DBArrayAlloc::alloc(count + 1, count, 1)); + } + void free(void* p) { + DBArrayAlloc::free(p, 1); + } + void destroy() noexcept; + char* construct_z(const char* s, size_t len); + char* construct_sv(const char* s, size_t len); private: - alignas(size_type) static constexpr char _empty_str[sizeof(size_type)] = {}; - char* _storage = empty_str(); + void* _storage = DBArrayAlloc::empty_buf(); }; // This should be used over the conversion operator, so we can track all dbstr -> str instances @@ -151,17 +156,17 @@ namespace lcf { inline DBString& DBString::operator=(DBString&& o) noexcept { if (this != &o) { - if (!empty()) { - _reset(); - } - _storage = o._storage; - o._storage = empty_str(); + destroy(); + swap(o); } return *this; } -inline DBString::size_type DBString::size() const { - return *(reinterpret_cast(_storage) - 1); +inline void DBString::destroy() noexcept { + if (_storage != DBArrayAlloc::empty_buf()) { + free(_storage); + _storage = DBArrayAlloc::empty_buf(); + } } } // namespace lcf diff --git a/tests/dbarray.cpp b/tests/dbarray.cpp new file mode 100644 index 000000000..0a82d2515 --- /dev/null +++ b/tests/dbarray.cpp @@ -0,0 +1,204 @@ +/* + * This file is part of liblcf. Copyright (c) 2020 liblcf authors. + * https://github.com/EasyRPG/liblcf - https://easyrpg.org + * + * liblcf is Free/Libre Open Source Software, released under the MIT License. + * For the full copyright and license information, please view the COPYING + * file that was distributed with this source code. + */ + +#include "lcf/dbarray.h" +#include "doctest.h" + +#include +#include +#include + +namespace lcf { +template class DBArray; +template class DBArray; +template class DBArray>; + +template +doctest::String toString(const DBArray& a) { + std::ostringstream ss; + ss << "{"; + auto iter = a.begin(); + if (iter != a.end()) { + ss << *iter; + ++iter; + } + while (iter != a.end()) { + ss << ", " << *iter; + } + ss << "}"; + return doctest::String(ss.str().c_str()); +} + +} + +using namespace lcf; + +TEST_SUITE_BEGIN("DBArray"); + +TEST_CASE_TEMPLATE("ConstructDef", T, int, bool, std::string) { + DBArray x; + + REQUIRE(x.empty()); + REQUIRE_EQ(x.size(), 0); +} + +TEST_CASE("Construct Ilist") { + DBArray x = {5}; + + REQUIRE(!x.empty()); + REQUIRE_EQ(x.size(), 1); + REQUIRE_EQ(x.front(), 5); + REQUIRE_EQ(x[0], 5); + REQUIRE_EQ(x.back(), 5); + + REQUIRE_EQ(x.data()[0], 5); + + auto iter = x.begin(); + REQUIRE_EQ(*iter, 5); + ++iter; + REQUIRE_EQ(iter, x.end()); +} + +TEST_CASE("Construct Ilist2") { + DBArray x = {5, 7}; + + REQUIRE(!x.empty()); + REQUIRE_EQ(x.size(), 2); + REQUIRE_EQ(x.front(), 5); + REQUIRE_EQ(x[0], 5); + REQUIRE_EQ(x[1], 7); + REQUIRE_EQ(x.back(), 7); + + REQUIRE_EQ(x.data()[0], 5); + REQUIRE_EQ(x.data()[1], 7); + + auto iter = x.begin(); + REQUIRE_EQ(*iter, 5); + ++iter; + REQUIRE_EQ(*iter, 7); + ++iter; + REQUIRE_EQ(iter, x.end()); +} + +TEST_CASE("Construct Count") { + DBArray x(3); + + REQUIRE(!x.empty()); + REQUIRE_EQ(x.size(), 3); + REQUIRE_EQ(x.front(), 0); + REQUIRE_EQ(x[0], 0); + REQUIRE_EQ(x[1], 0); + REQUIRE_EQ(x[2], 0); + REQUIRE_EQ(x.back(), 0); +} + +TEST_CASE("Construct Count T") { + DBArray x(3, 15); + + REQUIRE(!x.empty()); + REQUIRE_EQ(x.size(), 3); + REQUIRE_EQ(x.front(), 15); + REQUIRE_EQ(x[0], 15); + REQUIRE_EQ(x[1], 15); + REQUIRE_EQ(x[2], 15); + REQUIRE_EQ(x.back(), 15); +} + +TEST_CASE("Swap") { + const DBArray ca = {1}; + const DBArray cb = {2}; + + DBArray a = {1}; + DBArray b = {2}; + a.swap(b); + + REQUIRE_EQ(a, cb); + REQUIRE_EQ(b, ca); +} + +TEST_CASE("Cmp") { + const DBArray a = {1}; + const DBArray b = {2}; + + REQUIRE(a == a); + REQUIRE_FALSE(a != a); + REQUIRE_FALSE(a < a); + REQUIRE(a <= a); + REQUIRE_FALSE(a > a); + REQUIRE(a >= a); + + REQUIRE_FALSE(a == b); + REQUIRE(a != b); + REQUIRE(a < b); + REQUIRE(a <= b); + REQUIRE_FALSE(a > b); + REQUIRE_FALSE(a >= b); + + REQUIRE_FALSE(b == a); + REQUIRE(b != a); + REQUIRE_FALSE(b < a); + REQUIRE_FALSE(b <= a); + REQUIRE(b > a); + REQUIRE(b >= a); +} + + +TEST_CASE("Copy") { + const DBArray ca = {1}; + const DBArray cb = {2}; + + DBArray a = {1}; + DBArray b = {2}; + + b = a; + REQUIRE_EQ(a, ca); + REQUIRE_EQ(b, ca); + + b = a; + REQUIRE_EQ(a, ca); + REQUIRE_EQ(b, ca); + + b = b; + REQUIRE_EQ(a, ca); + REQUIRE_EQ(b, ca); + + DBArray c(b); + REQUIRE_EQ(a, ca); + REQUIRE_EQ(b, ca); + REQUIRE_EQ(c, ca); +} + +TEST_CASE("Move") { + const DBArray n = {}; + const DBArray ca = {1}; + const DBArray cb = {2}; + + DBArray a = {1}; + DBArray b = {2}; + + b = std::move(a); + REQUIRE_EQ(a, n); + REQUIRE_EQ(b, ca); + + b = std::move(b); + REQUIRE_EQ(a, n); + REQUIRE_EQ(b, ca); + + DBArray c(std::move(b)); + REQUIRE_EQ(a, n); + REQUIRE_EQ(b, n); + REQUIRE_EQ(c, ca); + + c = std::move(b); + REQUIRE_EQ(a, n); + REQUIRE_EQ(b, n); + REQUIRE_EQ(c, n); +} + +TEST_SUITE_END();