diff --git a/.clang-format b/.clang-format index 68ac916..2e9b678 100644 --- a/.clang-format +++ b/.clang-format @@ -47,7 +47,7 @@ EmptyLineBeforeAccessModifier: LogicalBlock FixNamespaceComments: true IncludeBlocks: Preserve IndentCaseLabels: true -IndentPPDirectives: AfterHash +IndentPPDirectives: None IndentWidth: 2 KeepEmptyLinesAtTheStartOfBlocks: true MaxEmptyLinesToKeep: 1 diff --git a/README.md b/README.md index b87249a..5643208 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,7 @@ efficiency and interface. The general namespace is `cxstructs`. -*Note: Currently the old datastructures are modernized with focus on better includes for compile times and correctness.* -*Non-Stack datastructure might not be usable for now* +*Note: Some datastructures are outdated* **1.** [Contents](#contents) **2.** [Usage Guide](#usage-guide) @@ -22,7 +21,7 @@ The general namespace is `cxstructs`. ### Speed Comparison -*Relative to the fastest / with CXPoolAllocator (if applicable)* + Note: *These are old benchmarks* | | vector | Stack | HashMap | StackHashMap | HashSet | LinkedList | Queue | DeQueue | |:----------------|:--------:|:--------:|:--------:|:------------:|:-------:|:----------:|:--------:|:--------:| @@ -57,26 +56,30 @@ The general namespace is `cxstructs`. #### Data Structures -- **Vector**(*vec*): -- **Matrix**(*mat*): *flattened float array, lots of methods* -- **Row**(*row*): *compile-time sized, non-mutable container* -- **Pair**: *static container for two types* -- **Trie**: *limited to ASCII (128)* -- **Stack**: -- **HashMap**: *using separate chaining with LinkedLists with static buffer* -- **HashSet**: *using separate chaining with LinkedLists with static buffer* -- **HashGrid**: *uses STL containers / 2D spatial lookups* -- **Linked List**: +- **HashGrid**: *Very fast cache friendly implementation* +- **SmallVector**: *Vector with configurable stack based storage buffer* +- **BitMask**: *various bit mask container* - **StackVector**: *stack container with std::vector interface* - **StackHashMap**: *(yes :) stack container with std::unordered map interface* -- **Double Linked List**: -- **Queue**: *using circular array* -- **DeQueue**: *using circular array* - **PriorityQueue**: *using binary heap* -- **Binary Tree**: -- **QuadTree**: *allows custom Types with x() and y() getters* -- **BitMask**: *various bit mask container* -- **Geometry**(*Rect,Circle,Point*): *standard efficient 2D shapes* + + +- **Outdated** + - **Vector**(*vec*): + - **Matrix**(*mat*): *flattened float array, lots of methods* + - **Row**(*row*): *compile-time sized, non-mutable container* + - **Pair**: *static container for two types* + - **Trie**: *limited to ASCII (128)* + - **Stack**: + - **HashMap**: *using separate chaining with LinkedLists with static buffer* + - **HashSet**: *using separate chaining with LinkedLists with static buffer* + - **Linked List**: + - **Double Linked List**: + - **Queue**: *using circular array* + - **DeQueue**: *using circular array* + - **Binary Tree**: + - **QuadTree**: *allows custom Types with x() and y() getters* + - **Geometry**(*Rect,Circle,Point*): *standard efficient 2D shapes* #### Machine Learning @@ -97,13 +100,17 @@ The general namespace is `cxstructs`. - **cxassert**: *custom assertions with optional text* - **cxbits**: *bit operations on numbers for embedding and retrieving information* -- **cxgraphics**: *simple native windowing and graphics output header* - **cxio**: *simple, readable and symmetric file io format* - **cxmath**: *activation functions,distance functions, next_power_of_2, square root* - **cxstring**: *operations on strings* - **cxtime**: *simple time measurements with multiple time points and formats* - **cxtips**: *collection of helpful resources and personal guidelines with examples* + +- **Outdated**: + - **cxgraphics**: *simple native windowing and graphics output header* + + --- ### Usage Guide @@ -219,5 +226,4 @@ for PriorityQueue) - The CX_ASSERT macro is inspired by the video ["How I use C++;a line-by-line code review"](https://www.youtube.com/watch?v=W8-G_PL6p-0&pp=ygUYbXkgYysrIGlzIGluc2FuZSBzdHJhZ2Vy) - by Strager - + by Strager \ No newline at end of file diff --git a/src/cxconfig.h b/src/cxconfig.h index ca8822c..6520588 100644 --- a/src/cxconfig.h +++ b/src/cxconfig.h @@ -19,10 +19,10 @@ // SOFTWARE. #define CX_FINISHED #ifndef CXSTRUCTS_SRC_CONFIG_H_ -# define CXSTRUCTS_SRC_CONFIG_H_ +#define CXSTRUCTS_SRC_CONFIG_H_ -# include //Used for almost all size_types -# include "cxutil/cxassert.h" //includes only +#include //Used for almost all size_types +#include "cxutil/cxassert.h" //includes only //-----------DEFINES-----------// @@ -31,28 +31,27 @@ // // -# define CX_INL inline -# define CX_NDISC [[nodiscard]] +#define CX_INL inline +#define CX_NDISC [[nodiscard]] // namespace for exposed structs and functions namespace cxstructs {} +#ifdef CX_STACK_ABORT +#define CX_STACK_ABORT_IMPL() \ + if (size_ >= N) std::abort() +#else +#define CX_STACK_ABORT_IMPL() (void(0)) +#endif -# ifdef CX_STACK_ABORT -# define CX_STACK_ABORT_IMPL() \ - if (size_ >= N) std::abort() -# else -# define CX_STACK_ABORT_IMPL() (void(0)) -# endif - -# ifndef CX_USE_INT +#ifndef CX_USE_INT using uint_32_cx = uint_fast32_t; using uint_16_cx = uint_fast16_t; using int_32_cx = int_fast32_t; -# else +#else typedef int uint_32_cx; typedef int uint_16_cx; typedef int int_32_cx; -# endif +#endif #endif //CXSTRUCTS_SRC_CONFIG_H_ \ No newline at end of file diff --git a/src/cxstructs/BinaryTree.h b/src/cxstructs/BinaryTree.h index 4fb4148..23e67b2 100644 --- a/src/cxstructs/BinaryTree.h +++ b/src/cxstructs/BinaryTree.h @@ -19,16 +19,11 @@ // SOFTWARE. #define CX_FINISHED #ifndef CXSTRUCTS_BINARYTREE_H -# define CXSTRUCTS_BINARYTREE_H +#define CXSTRUCTS_BINARYTREE_H -# include -# include -# include -# include -# include -# include "../cxconfig.h" +#include "../cxconfig.h" -namespace cxhelper { // namespace to hide helper structs +namespace cxhelper { /** * Helper struct for the BinaryTree * @tparam T type @@ -38,10 +33,9 @@ struct TreeNode { TreeNode* left_; TreeNode* right_; T data_; - inline explicit TreeNode(const T& val) : data_(val), left_(nullptr), right_(nullptr) {} + explicit TreeNode(const T& val) : data_(val), left_(nullptr), right_(nullptr) {} - inline TreeNode(const T& val, TreeNode* left, TreeNode* right) - : data_(val), left_(left), right_(right) {} + TreeNode(const T& val, TreeNode* left, TreeNode* right) : data_(val), left_(left), right_(right) {} }; } // namespace cxhelper namespace cxstructs { @@ -65,7 +59,7 @@ class BinaryTree { TNode* root_; uint_32_cx size_; - inline int subTreeDepth(TNode* node) { + int subTreeDepth(TNode* node) { if (!node) { return 0; } else { @@ -74,7 +68,7 @@ class BinaryTree { return std::max(left, right) + 1; } } - inline void insert(const T& val, TNode* node) { + void insert(const T& val, TNode* node) { if (val < node->data_) { if (!node->left_) { node->left_ = new TNode(val); @@ -85,7 +79,7 @@ class BinaryTree { } else insert(val, node->right_); } } - inline bool contains(const T& val, TNode* node) const { + bool contains(const T& val, TNode* node) const { if (node) { if (val < node->data_) { if (node->left_) { @@ -103,7 +97,7 @@ class BinaryTree { } return false; } - inline TNode* erase(const T& val, TNode* node) { + TNode* erase(const T& val, TNode* node) { if (!node) { return node; } @@ -131,7 +125,7 @@ class BinaryTree { } return node; } - inline TNode* minValueNode(TNode* node) { + TNode* minValueNode(TNode* node) { TNode* current = node; while (current && current->left_ != nullptr) { @@ -142,7 +136,6 @@ class BinaryTree { public: BinaryTree() : root_(nullptr), size_(0){}; - //no moving around yet BinaryTree(const BinaryTree&) = delete; BinaryTree& operator=(const BinaryTree&) = delete; BinaryTree(BinaryTree&&) = delete; @@ -174,7 +167,7 @@ class BinaryTree { /** * Inverts this BinaryTree starting from the given node */ - inline void invert(TNode* node) { + void invert(TNode* node) { if (node == nullptr) { return; } @@ -186,13 +179,13 @@ class BinaryTree { /** * Inverts this BinaryTree starting from the root */ - inline void invert() { invert(root_); } + void invert() { invert(root_); } /** * Inserts the given element into the tree at the right position * @param val - the inserted value */ - inline void insert(const T& val) { + void insert(const T& val) { if (root_) { insert(val, root_); size_++; @@ -207,14 +200,14 @@ class BinaryTree { * @param val - the value to search for * @return - true if the tree contained the given value, false otherwise */ - [[nodiscard]] inline bool contains(const T& val) const { return contains(val, root_); } + [[nodiscard]] bool contains(const T& val) const { return contains(val, root_); } /** * Erases the first occurrence of a node with this value * @param val - the node-value to search for * @return true if a deletion happened */ - inline bool erase(const T& val) { + bool erase(const T& val) { auto temp = size_; root_ = erase(val, root_); return (temp - 1 == size_); @@ -223,7 +216,7 @@ class BinaryTree { @brief Removes all nodes from the binary tree

After the operation, the tree becomes empty and its size is 0 **/ - inline void clear() { + void clear() { std::deque nodesToDelete; nodesToDelete.push_back(root_); @@ -252,7 +245,7 @@ class BinaryTree { * * @return true if the BinaryTree is empty, false otherwise. */ - [[nodiscard]] inline bool empty() const { return size_ == 0; } + [[nodiscard]] bool empty() const { return size_ == 0; } /** * * @return the maximum depth of the tree @@ -295,7 +288,7 @@ class BinaryTree { */ InOrderIterator end() { return InOrderIterator(nullptr); } -# ifdef CX_INCLUDE_TESTS +#ifdef CX_INCLUDE_TESTS static void TEST() { std::cout << "BINARY SEARCH TREE TESTS" << std::endl; @@ -376,7 +369,7 @@ class BinaryTree { prev_num = num; } } -# endif +#endif }; } // namespace cxstructs -#endif // CXSTRUCTS_BINARYTREE_H +#endif // CXSTRUCTS_BINARYTREE_H \ No newline at end of file diff --git a/src/cxstructs/BitMask.h b/src/cxstructs/BitMask.h index 4bcbc74..469d777 100644 --- a/src/cxstructs/BitMask.h +++ b/src/cxstructs/BitMask.h @@ -19,58 +19,19 @@ // SOFTWARE. #define FINISHED #ifndef CXSTRUCTS_SRC_CXSTRUCTS_BITFLAG_H_ -# define CXSTRUCTS_SRC_CXSTRUCTS_BITFLAG_H_ +#define CXSTRUCTS_SRC_CXSTRUCTS_BITFLAG_H_ -# include "../cxconfig.h" -# include -# include +#include "../cxconfig.h" +#include namespace cxstructs { -//-----------BLUEPRINT-----------// -/* -enum class MyFlags : uint32_t { -Flag1 = 1 << 0, -Flag2 = 1 << 1, -Flag3 = 1 << 2, -Flag4 = 1 << 3, -Flag5 = 1 << 4, -Flag6 = 1 << 5, -Flag7 = 1 << 6, -Flag8 = 1 << 7, -Flag9 = 1 << 8, -Flag10 = 1 << 9, -Flag11 = 1 << 10, -Flag12 = 1 << 11, -Flag13 = 1 << 12, -Flag14 = 1 << 13, -Flag15 = 1 << 14, -Flag16 = 1 << 15, -Flag17 = 1 << 16, -Flag18 = 1 << 17, -Flag19 = 1 << 18, -Flag20 = 1 << 19, -Flag21 = 1 << 20, -Flag22 = 1 << 21, -Flag23 = 1 << 22, -Flag24 = 1 << 23, -Flag25 = 1 << 24, -Flag26 = 1 << 25, -Flag27 = 1 << 26, -Flag28 = 1 << 27, -Flag29 = 1 << 28, -Flag30 = 1 << 29, -Flag31 = 1 << 30, -Flag32 = 1U << 31, -}; - */ - template struct SuitableStorageType { // Default to uint64_t for N <= 64 - using type = std::conditional_t< - N <= 8, uint8_t, - std::conditional_t>>; + using type = + std::conditional_t>>; }; /** @@ -81,45 +42,41 @@ struct SuitableStorageType { * @tparam E the enum * @tparam MAX_FLAGS maximum amount of expected flags (can be different from enum type) */ -template +template class EnumMask { - private: - using StorageType = typename SuitableStorageType::type; - StorageType data_ = 0; + E data_ = 0; public: - inline void set(E flag) noexcept { data_ |= static_cast(flag); } + void set(E flag) noexcept { data_ |= static_cast(flag); } - inline void unset(E flag) noexcept { data_ &= ~static_cast(flag); } + void unset(E flag) noexcept { data_ &= ~static_cast(flag); } - inline void toggle(E flag) noexcept { data_ ^= static_cast(flag); } + void toggle(E flag) noexcept { data_ ^= static_cast(flag); } - [[nodiscard]] inline bool isSet(E flag) const noexcept { - return (data_ & static_cast(flag)) != 0; - } + [[nodiscard]] bool isSet(E flag) const noexcept { return (data_ & static_cast(flag)) != 0; } - inline void clear() noexcept { data_ = 0; } + void clear() noexcept { data_ = 0; } - [[nodiscard]] inline bool any() const noexcept { return data_ != 0x0; } + [[nodiscard]] bool any() const noexcept { return data_ != 0x0; } //Compile time check(unfolding) template [[nodiscard]] constexpr bool any_of() const noexcept { - StorageType compositeFlag = (0 | ... | static_cast(Flags)); + E compositeFlag = (0 | ... | static_cast(Flags)); return (data_ & compositeFlag) != 0; } //Compile time check(unfolding) template [[nodiscard]] constexpr bool all_of() const noexcept { - StorageType compositeFlag = (0 | ... | static_cast(Flags)); + E compositeFlag = (0 | ... | static_cast(Flags)); return (data_ & compositeFlag) == compositeFlag; } //Runtime check - [[nodiscard]] inline bool any_of(std::initializer_list flags) const noexcept { + [[nodiscard]] bool any_of(std::initializer_list flags) const noexcept { for (auto flag : flags) { - if ((data_ & static_cast(flag)) != 0) { + if ((data_ & static_cast(flag)) != 0) { return true; } } @@ -127,9 +84,9 @@ class EnumMask { } //Runtime check - [[nodiscard]] inline bool all_of(std::initializer_list flags) const noexcept { + [[nodiscard]] bool all_of(std::initializer_list flags) const noexcept { for (auto flag : flags) { - if ((data_ & static_cast(flag)) == 0) { + if ((data_ & static_cast(flag)) == 0) { return false; } } @@ -137,7 +94,7 @@ class EnumMask { } }; -# ifdef CX_INCLUDE_TESTS +#ifdef CX_INCLUDE_TESTS static void TEST_FLAG_CONTAINERS() { enum Test { ONE = 1, TWO = 2, THREE = 4 }; @@ -159,7 +116,44 @@ static void TEST_FLAG_CONTAINERS() { res = flag.all_of(); CX_ASSERT(res, ""); } -# endif +#endif +//-----------BLUEPRINT-----------// +/* +enum class MyFlags : uint32_t { +Flag1 = 1 << 0, +Flag2 = 1 << 1, +Flag3 = 1 << 2, +Flag4 = 1 << 3, +Flag5 = 1 << 4, +Flag6 = 1 << 5, +Flag7 = 1 << 6, +Flag8 = 1 << 7, +Flag9 = 1 << 8, +Flag10 = 1 << 9, +Flag11 = 1 << 10, +Flag12 = 1 << 11, +Flag13 = 1 << 12, +Flag14 = 1 << 13, +Flag15 = 1 << 14, +Flag16 = 1 << 15, +Flag17 = 1 << 16, +Flag18 = 1 << 17, +Flag19 = 1 << 18, +Flag20 = 1 << 19, +Flag21 = 1 << 20, +Flag22 = 1 << 21, +Flag23 = 1 << 22, +Flag24 = 1 << 23, +Flag25 = 1 << 24, +Flag26 = 1 << 25, +Flag27 = 1 << 26, +Flag28 = 1 << 27, +Flag29 = 1 << 28, +Flag30 = 1 << 29, +Flag31 = 1 << 30, +Flag32 = 1U << 31, +}; + */ } // namespace cxstructs -#endif //CXSTRUCTS_SRC_CXSTRUCTS_BITFLAG_H_ +#endif //CXSTRUCTS_SRC_CXSTRUCTS_BITFLAG_H_ \ No newline at end of file diff --git a/src/cxstructs/HashGrid.h b/src/cxstructs/HashGrid.h index b54d34b..722dd94 100644 --- a/src/cxstructs/HashGrid.h +++ b/src/cxstructs/HashGrid.h @@ -18,129 +18,194 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #define CX_FINISHED -#ifndef CXSTRUCTS_SRC_CXSTRUCTS_HASHGRID_H_ -# define CXSTRUCTS_SRC_CXSTRUCTS_HASHGRID_H_ +#ifndef CXSTRUCTS_MULTIRESOLUTIONGRID_H +#define CXSTRUCTS_MULTIRESOLUTIONGRID_H + +#include +#include +#include + +// IMPORTANT: Use a custom hashmap here (dense map optimally) and possibly a custom vector +// You can then remove the includes +template +using HashMapType = std::unordered_map; // Insert custom type here + +template +using VectorType = std::vector; // Inset custom type here + +// This is a cache friendly "top-level" data structure +// https://stackoverflow.com/questions/41946007/efficient-and-well-explained-implementation-of-a-quadtree-for-2d-collision-det +// Originally inspired by the above post to just move all the data of the structure to the top level +// This simplifies memory management and layout, it uses just a single vector for data +// This is achieve by mapping between dimensions, here between a cell and a memory block +// Now its still not perfect but definitely very fast +// Also the problem of multiple insertions is efficiently solved by accumulating with a hashmap +// Its almost mandatory to use a memory consistent map like a dense map thats a vector internally aswell +// This simplifies memory and thus cache friendlyness even more +// With this setup you have 0 (zero) allocations in game ticks which involves completely clearing and refilling grid +namespace cxstructs { +using CellID = uint64_t; +// This creates a unique value - both values are unqiue themselves so their concatenated version is aswell +// We still use a hash function on it to get more byte range (all bits flipped) +inline CellID GetCellID(const int cellX, const int cellY) { + return static_cast(cellX) << 32 | cellY; +} + +template +struct DataBlock final { + static constexpr int NO_NEXT_BLOCK = UINT16_MAX; + T data[size]; // Fixed size data block + uint16_t count = 0; // Current number of elements + uint16_t next = NO_NEXT_BLOCK; // Index of the next block or -1 if if its the end + + [[nodiscard]] bool isFull() const { return count == size; } + // Can happen that its full but no next one is inserted yet + [[nodiscard]] bool hasNext() const { return next != NO_NEXT_BLOCK; } + + void add(T val) { + assert(count < size); + data[count++] = val; + } + template + void append(Container& elems) const { + for (int i = 0; i < count; ++i) { + elems.insert(data[i]); + } + } +}; -# include -# include -# include "../cxconfig.h" +template +struct SingleResolutionHashGrid final { + HashMapType cellMap{}; + VectorType> dataBlocks{}; + int cellSize; -namespace cxstructs { + explicit SingleResolutionHashGrid(const int cellSize) : cellSize(cellSize) {} -/** - * HashGrid for a square space - * A HashGrid divides the space into cells of uniform size, and entities are assigned to these cells based on their spatial coordinates.
- * This spatial partitioning allows for efficient querying and management of entities based on their locations. - * - */ -template -struct HashGrid { - using size_type = uint_32_cx; - using GridID = size_type; + void insert(V val, const float x, const float y, const int w, const int h) { + int x1 = static_cast(x) / cellSize; + int y1 = static_cast(y) / cellSize; + int x2 = (static_cast(x) + w) / cellSize; + int y2 = (static_cast(y) + h) / cellSize; - private: - float cellSize = 0; - float totalSpaceSize = 0; - size_type gridSize = 0; - std::unordered_map> cells; - - public: - explicit HashGrid(float cellSize, float spaceSize) - : cellSize(cellSize), totalSpaceSize(spaceSize), - gridSize(static_cast(spaceSize / cellSize)) { - cells.reserve(gridSize * gridSize); - cells.max_load_factor(1.0F); - }; - HashGrid() = default; - HashGrid(const HashGrid&) = delete; - HashGrid& operator=(const HashGrid&) = delete; - HashGrid(HashGrid&&) = delete; - HashGrid& operator=(HashGrid&& other) noexcept { - if (this == &other) { - return *this; - } + CellID cellID = GetCellID(x1, y1); + insertElement(cellID, val); - cells = std::move(other.cells); - cellSize = other.cellSize; - totalSpaceSize = other.totalSpaceSize; - gridSize = other.gridSize; + if (x1 != x2) [[unlikely]] { + cellID = GetCellID(x2, y1); + insertElement(cellID, val); - other.cellSize = 0; - other.totalSpaceSize = 0; - other.gridSize = 0; + if (y1 != y2) [[unlikely]] { + cellID = GetCellID(x1, y2); + insertElement(cellID, val); - return *this; - } - inline const std::vector& operator[](GridID g) noexcept { return cells[g]; } - [[nodiscard]] inline GridID getGridID(float x, float y) const noexcept { - return static_cast(x / cellSize) + static_cast(y / cellSize) * gridSize; - } - inline void getGridIDs(int32_t (&gridIDs)[4], float x, float y, float width, - float height) const noexcept { - int topLeftGridX = static_cast(x / cellSize); - int topLeftGridY = static_cast(y / cellSize); - int bottomRightGridX = static_cast((x + width) / cellSize); - int bottomRightGridY = static_cast((y + height) / cellSize); - - std::fill(std::begin(gridIDs), std::end(gridIDs), -1); - - gridIDs[0] = topLeftGridX + topLeftGridY * gridSize; - gridIDs[1] = (topLeftGridX != bottomRightGridX) ? bottomRightGridX + topLeftGridY * gridSize - : -1; // Top-right - gridIDs[2] = (topLeftGridY != bottomRightGridY) ? topLeftGridX + bottomRightGridY * gridSize - : -1; // Bottom-left - gridIDs[3] = (topLeftGridX != bottomRightGridX && topLeftGridY != bottomRightGridY) - ? bottomRightGridX + bottomRightGridY * gridSize - : -1; // Bottom-right + cellID = GetCellID(x2, y2); + insertElement(cellID, val); + return; + } + } + + if (y1 != y2) [[unlikely]] { + cellID = GetCellID(x1, y2); + insertElement(cellID, val); + } } - inline void clear() { - for (auto& pair : cells) { - pair.second.clear(); + + template + void query(Container& elems, const float x, const float y, const int w, const int h) { + const int x1 = static_cast(x) / cellSize; + const int y1 = static_cast(y) / cellSize; + const int x2 = (static_cast(x) + w) / cellSize; + const int y2 = (static_cast(y) + h) / cellSize; + + CellID cellID = GetCellID(x1, y1); + queryElements(cellID, elems); + + if (x1 != x2) [[unlikely]] { + cellID = GetCellID(x2, y1); + queryElements(cellID, elems); + + if (y1 != y2) [[unlikely]] { + cellID = GetCellID(x1, y2); + queryElements(cellID, elems); + + cellID = GetCellID(x2, y2); + queryElements(cellID, elems); + return; + } } - }; - inline void setupNew(float newCellSize, uint_16_cx newSpaceSize, bool optimized = true) { - if (optimized) { - float value = cellSize / totalSpaceSize; - cellSize = newSpaceSize * value; - cells.reserve(std::pow(totalSpaceSize / cellSize, 2)); - } else { - cellSize = newCellSize; - totalSpaceSize = newSpaceSize; + + if (y1 != y2) [[unlikely]] { + cellID = GetCellID(x1, y2); + queryElements(cellID, elems); } - cells.clear(); } - inline void insert(float x, float y, EntityID entityID) { - CX_ASSERT(x < totalSpaceSize && y < totalSpaceSize, "x or y is larger than spaceSize"); - cells[getGridID(x, y)].emplace_back(entityID); + + void clear() { + cellMap.clear(); + dataBlocks.clear(); } -# ifdef CX_INCLUDE_TESTS - static void TEST() { - float spaceSize = 100; - float cellSize = 5; + void reserve(const int cells, const int expectedTotalEntites) { + cellMap.reserve(cells); + dataBlocks.reserve(expectedTotalEntites / blockSize); + } - HashGrid hashGrid{cellSize, spaceSize}; + private: + void insertElement(const CellID id, V val) { + const auto it = cellMap.find(id); + int blockIdx; + if (it == cellMap.end()) [[unlikely]] // Most elements should be together + { + blockIdx = static_cast(dataBlocks.size()); + cellMap.insert({id, blockIdx}); + dataBlocks.push_back({}); + } else { + blockIdx = it->second; + } - for (uint_fast32_t i = 0; i < spaceSize; i++) { - for (uint_fast32_t j = 0; j < spaceSize; j++) { - hashGrid.insert(i, j, i); - } + auto* block = &dataBlocks[blockIdx]; + + while (block->hasNext()) { + block = &dataBlocks[block->next]; } - hashGrid.insert(99, 99, 1); - hashGrid.insert(1, 1, 2); + if (block->isFull()) [[unlikely]] // Only happens once each block + { + const auto nextIdx = static_cast(dataBlocks.size()); + block->next = nextIdx; + dataBlocks.push_back({}); + // Re allocation can invalidate the reference !!!! + block = &dataBlocks[nextIdx]; + } - std::vector coll; - coll.reserve(60); + block->add(val); + } - now(); - hashGrid.containedInRectCollect(0, 0, 99, 99, coll); - printTime("lookup"); - std::cout << coll.size() << std::endl; + template + void queryElements(const CellID id, Container& elems) { + const auto it = cellMap.find(id); + if (it == cellMap.end()) [[unlikely]] // Most elements should be together + { + return; + } + const int blockIdx = it->second; + DataBlock* startBlock = nullptr; + startBlock = &dataBlocks[blockIdx]; + + startBlock->append(elems); + while (startBlock->hasNext()) { + assert(startBlock->isFull()); + startBlock = &dataBlocks[startBlock->next]; + startBlock->append(elems); + } } -# endif + + static_assert(std::is_trivially_constructible_v && std::is_trivially_destructible_v); + static_assert(sizeof(V) <= 8, "You should only use small id types"); }; +template +using HashGrid = SingleResolutionHashGrid; } // namespace cxstructs - -#endif //CXSTRUCTS_SRC_CXSTRUCTS_HASHGRID_H_ +#endif //CXSTRUCTS_MULTIRESOLUTIONGRID_H \ No newline at end of file diff --git a/src/cxstructs/SmallVector.h b/src/cxstructs/SmallVector.h new file mode 100644 index 0000000..4e8dae3 --- /dev/null +++ b/src/cxstructs/SmallVector.h @@ -0,0 +1,100 @@ +// Copyright (c) 2023 gk646 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#define CX_FINISHED +#ifndef SMALLVECTOR_H +#define SMALLVECTOR_H + +#include + +namespace cxstructs { +template +struct SmallVector { + SmallVector() : m_size(0), m_capacity(N), m_data(m_stack_data) {} + + ~SmallVector() { + if (m_data != m_stack_data) { + delete[] m_data; + } + } + + void push_back(const T& value) { + ensure_capacity(m_size + 1); + m_data[m_size++] = value; + } + + void pop_back() { + assert(m_size > 0); + --m_size; + } + + // ensures capacity - initializes all new values + void resize(size_type new_size, const T& val) { + if (new_size > m_size) { + ensure_capacity(new_size); + for (size_type i = m_size; i < new_size; ++i) { + m_data[i] = val; + } + m_size = new_size; + } + } + + T& operator[](size_type index) { + assert(index < m_size); + return m_data[index]; + } + + const T& operator[](size_type index) const { + assert(index < m_size); + return m_data[index]; + } + + size_type size() const { return m_size; } + + size_type capacity() const { return m_capacity; } + + const T* data() const { return m_data; } + + T* data() { return m_data; } + + private: + void ensure_capacity(size_type new_capacity) { + if (new_capacity <= m_capacity) { + return; + } + + new_capacity = m_capacity * 2; + T* new_data = new T[new_capacity]; + + std::memcpy(new_data, m_data, m_size * sizeof(T)); + + if (m_data != m_stack_data) { + delete[] m_data; + } + + m_data = new_data; + m_capacity = new_capacity; + } + size_type m_size; + size_type m_capacity; + T* m_data; + T m_stack_data[N]; +}; +} // namespace cxstructs +#endif //SMALLVECTOR_H \ No newline at end of file diff --git a/src/cxutil/cxio.h b/src/cxutil/cxio.h index 865a8c4..4bbaf3f 100644 --- a/src/cxutil/cxio.h +++ b/src/cxutil/cxio.h @@ -19,10 +19,10 @@ // SOFTWARE. #define CX_FINISHED #ifndef CXSTRUCTS_SRC_CXIO_H_ -# define CXSTRUCTS_SRC_CXIO_H_ +#define CXSTRUCTS_SRC_CXIO_H_ -# include "../cxconfig.h" -# include +#include "../cxconfig.h" +#include // Simple, readable and fast *symmetric* serialization structure with loading // and saving. Each line is a concatenated list of values and a separator @@ -34,7 +34,7 @@ namespace cxstructs { static constexpr int MAX_SECTION_SIZE = 16; -# define NEW_LINE_SUB '\036' +#define NEW_LINE_SUB '\036' //-----------SHARED-----------// namespace { @@ -121,14 +121,16 @@ template // SaveFunc(FILE* file) bool io_save_buffered_write(const char* fileName, const int memoryBufferBytes, SaveFunc func) { CX_ASSERT(memoryBufferBytes > 0, "Given buffer size invalid"); -# ifdef _WIN32 +#ifdef _WIN32 FILE* file; fopen_s(&file, "NUL", "wb"); -# else +#else FILE* file = fopen("/dev/null", "wb"); -# endif +#endif // Write to in memory buffer - if (file == nullptr) { return false; } + if (file == nullptr) { + return false; + } auto* buffer = new char[memoryBufferBytes]; std::memset(buffer, 0, memoryBufferBytes); @@ -149,11 +151,11 @@ bool io_save_buffered_write(const char* fileName, const int memoryBufferBytes, S // When successful, open the actual save file and save the data const int dataSize = (int)strlen(buffer); -# ifdef _WIN32 +#ifdef _WIN32 fopen_s(&file, fileName, "wb"); -# else +#else file = fopen(fileName, "wb"); -# endif +#endif if (file == nullptr) { delete[] buffer; return false; @@ -169,7 +171,9 @@ bool io_save_buffered_write(const char* fileName, const int memoryBufferBytes, S delete[] buffer; - if (fclose(file) != 0) { return false; } + if (fclose(file) != 0) { + return false; + } return true; } @@ -224,23 +228,27 @@ inline bool io_load_inside_section(FILE* file, const char* section) { return true; // Still inside same section } // include to use -# if defined(_STRING_) || defined(_GLIBCXX_STRING) +#if defined(_STRING_) || defined(_GLIBCXX_STRING) inline void io_load(FILE* file, std::string& s) { //s.reserve(reserve_amount); // Dont need to reserve - string shouldnt allocate below 15 characters char ch; while (fread(&ch, 1, 1, file) == 1 && ch != '\037') { - if (ch == NEW_LINE_SUB) [[unlikely]] { ch = '\n'; } + if (ch == NEW_LINE_SUB) [[unlikely]] { + ch = '\n'; + } s.push_back(ch); } while (ch != '\37' && fread(&ch, 1, 1, file) == 1) {} } -# endif +#endif // Load a string property into a user-supplied buffer - return bytes written - reads until linesep is found inline int io_load(FILE* file, char* buffer, size_t buffer_size) { int count = 0; char ch; while (count < buffer_size - 1 && fread(&ch, 1, 1, file) == 1 && ch != '\037') { - if (ch == NEW_LINE_SUB) [[unlikely]] { ch = '\n'; } + if (ch == NEW_LINE_SUB) [[unlikely]] { + ch = '\n'; + } buffer[count++] = ch; } buffer[count] = '\0'; @@ -272,10 +280,10 @@ inline void io_load(FILE* file, float& f, float& f2, float& f3) { inline void io_load(FILE* file, float& f, float& f2) { fscanf(file, "%f;%f\037", &f, &f2); } -} +} // namespace cxstructs -# ifdef CX_INCLUDE_TESTS -# include +#ifdef CX_INCLUDE_TESTS +#include namespace cxtests { using namespace cxstructs; using namespace std::chrono; @@ -446,5 +454,5 @@ static void TEST_IO() { delete_test_files(); } } // namespace cxtests -# endif +#endif #endif // CXSTRUCTS_SRC_CXIO_H_ \ No newline at end of file diff --git a/src/cxutil/cxstring.h b/src/cxutil/cxstring.h index cdb9d9c..cdb22a4 100644 --- a/src/cxutil/cxstring.h +++ b/src/cxutil/cxstring.h @@ -19,13 +19,12 @@ // SOFTWARE. #define CX_FINISHED #ifndef CXSTRUCTS_SRC_CXUTIL_CXSTRING_H_ -# define CXSTRUCTS_SRC_CXUTIL_CXSTRING_H_ +#define CXSTRUCTS_SRC_CXUTIL_CXSTRING_H_ - -# include -# include -# include -# include +#include +#include +#include +#include namespace cxstructs { // Pads the given string "arg" inside "buff" with the "padSymbol" - optional prefix and suffix @@ -42,10 +41,14 @@ inline void str_pad(char* buff, const int size, const char* arg, const char padS currPos += snprintf(buff + currPos, size - currPos, "%s", arg); currPos = currPos > size ? size : currPos; } - if (suffix && currPos < size) { snprintf(buff + currPos, size - currPos, "%s", suffix); } + if (suffix && currPos < size) { + snprintf(buff + currPos, size - currPos, "%s", suffix); + } for (int i = currPos; i < size - 1; i++) { - if (buff[i] == '\0') { buff[i] = padSymbol; } + if (buff[i] == '\0') { + buff[i] = padSymbol; + } } buff[size - 1] = '\0'; } @@ -309,7 +312,7 @@ struct Fnv1aHash { struct StrEqual { bool operator()(const char* s1, const char* s2) const { return std::strcmp(s1, s2) == 0; } }; -# if defined(_STRING_) || defined(_GLIBCXX_STRING) +#if defined(_STRING_) || defined(_GLIBCXX_STRING) // Replaces the string with the string representation of the given number inline void str_embed_num(std::string& s, float num) { s.clear(); @@ -317,13 +320,13 @@ inline void str_embed_num(std::string& s, float num) { snprintf(buff, sizeof(buff), "%.6f", num); s.append(buff); } -# endif +#endif } // namespace cxstructs -# ifdef CX_INCLUDE_TESTS -# include -# include -# include "../cxconfig.h" +#ifdef CX_INCLUDE_TESTS +#include +#include +#include "../cxconfig.h" namespace cxtests { static void TEST_STRING() { @@ -351,6 +354,6 @@ static void TEST_STRING() { CX_ASSERT(it != myMap.end(), ""); } } // namespace cxtests -# endif +#endif #endif //CXSTRUCTS_SRC_CXUTIL_CXSTRING_H_ \ No newline at end of file diff --git a/src/cxutil/cxtime.h b/src/cxutil/cxtime.h index 09ac9b1..b89358d 100644 --- a/src/cxutil/cxtime.h +++ b/src/cxutil/cxtime.h @@ -19,11 +19,11 @@ // SOFTWARE. #define CX_FINISHED #ifndef CX_TIME_H -# define CX_TIME_H +#define CX_TIME_H -# include "../cxconfig.h" -# include -# include +#include "../cxconfig.h" +#include +#include namespace cxstructs { using namespace std; //std:: makes this code unreadable @@ -67,17 +67,10 @@ void printTime(const char* prefix = nullptr, const int checkpoint = 0) { const auto diffInDesiredUnits = std::chrono::duration_cast(diff); if (prefix) { -# if _HAS_CXX23 - std::print("{} ", prefix); -# else printf("%s", prefix); -# endif } -# if _HAS_CXX23 - std::print("{} {}\n", diffInDesiredUnits.count(), get_duration_unit()); -# else + printf("%lld %s\n", diffInDesiredUnits.count(), get_duration_unit()); -# endif } template > auto getTime(const int checkpoint = 0) -> long long { @@ -87,4 +80,4 @@ auto getTime(const int checkpoint = 0) -> long long { } } // namespace cxstructs -#endif //CX_TIME_H +#endif //CX_TIME_H \ No newline at end of file diff --git a/src/cxutil/cxtips.h b/src/cxutil/cxtips.h index f0c57d4..f74e76f 100644 --- a/src/cxutil/cxtips.h +++ b/src/cxutil/cxtips.h @@ -19,7 +19,7 @@ // SOFTWARE. #define CX_FINISHED #ifndef CXSTRUCTS_SRC_CXUTIL_CXTIPS_H_ -# define CXSTRUCTS_SRC_CXUTIL_CXTIPS_H_ +#define CXSTRUCTS_SRC_CXUTIL_CXTIPS_H_ //Links provided without warranty of function, implied ownership or support of the linked website @@ -131,5 +131,4 @@ // if(x > 0 &&width == 50){} // - #endif //CXSTRUCTS_SRC_CXUTIL_CXTIPS_H_ \ No newline at end of file