Compare commits
10 Commits
90a2dcd67a
...
860bb2d35a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
860bb2d35a | ||
|
|
51f4936aff | ||
|
|
41bdd79f27 | ||
|
|
14aa93ada0 | ||
|
|
de3638c8ad | ||
|
|
c3f8b2760d | ||
|
|
c255042796 | ||
|
|
bc9d7e3b9b | ||
|
|
d9a83f8822 | ||
| 34a246bac4 |
@@ -8,6 +8,8 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <nd-wfc/wfc.h>
|
||||
|
||||
// Forward declarations
|
||||
struct NonogramHints;
|
||||
struct NonogramSolution;
|
||||
@@ -178,3 +180,7 @@ std::string trim(const std::string& str);
|
||||
bool startsWith(const std::string& str, const std::string& prefix);
|
||||
std::vector<std::string> split(const std::string& str, char delimiter);
|
||||
std::vector<uint8_t> parseNumberSequence(const std::string& str, char delimiter);
|
||||
|
||||
using NonogramWFC = WFC::Builder<Nonogram, bool>
|
||||
::Define<false, true>
|
||||
::Build;
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <cctype>
|
||||
#include <string_view>
|
||||
|
||||
#include <nd-wfc/wfc.hpp>
|
||||
#include <nd-wfc/wfc.h>
|
||||
|
||||
// 4-bit packed Sudoku board storage - optimal packing
|
||||
// 81 cells * 4 bits = 324 bits
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
uint8_t result = (data[byteIndex] >> shiftAmount) & 0xF;
|
||||
|
||||
// Debug assertion: ensure result is in valid range
|
||||
WFC::constexpr_assert(result >= 0 && result <= 9, "Sudoku cell value must be between 0-9");
|
||||
WFC::constexpr_assert(result <= 9, "Sudoku cell value must be between 0-9");
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
// Optimization: (pos & 1) << 2 instead of (pos % 2) * 4
|
||||
constexpr inline void set(int pos, uint8_t value) {
|
||||
// Assert that value is in valid Sudoku range (0-9)
|
||||
WFC::constexpr_assert(value >= 0 && value <= 9, "Sudoku cell value must be between 0-9");
|
||||
WFC::constexpr_assert(value <= 9, "Sudoku cell value must be between 0-9");
|
||||
|
||||
int byteIndex = pos >> 1; // pos / 2 using right shift
|
||||
|
||||
@@ -279,21 +279,22 @@ private:
|
||||
public: // WFC Support
|
||||
using ValueType = uint8_t;
|
||||
|
||||
constexpr inline ValueType getValue(size_t index) const {
|
||||
constexpr inline ValueType getValue(uint8_t index) const {
|
||||
return board_.get(static_cast<int>(index));
|
||||
}
|
||||
|
||||
constexpr inline void setValue(size_t index, ValueType value) {
|
||||
constexpr inline void setValue(uint8_t index, ValueType value) {
|
||||
board_.set(static_cast<int>(index), value);
|
||||
}
|
||||
|
||||
constexpr inline size_t size() const {
|
||||
constexpr inline uint8_t size() const {
|
||||
return 81;
|
||||
}
|
||||
};
|
||||
|
||||
// Static assert to ensure correct size (now 56 bytes with solver additions)
|
||||
static_assert(sizeof(Sudoku) == 41, "Sudoku class must be exactly 41 bytes");
|
||||
static_assert(WFC::HasConstexprSize<Sudoku>, "Sudoku class must have a constexpr size() method");
|
||||
|
||||
// Fast solution validator (stateless)
|
||||
class SudokuValidator {
|
||||
@@ -314,20 +315,19 @@ public:
|
||||
private:
|
||||
static bool parseLine(const std::string& line, std::array<uint8_t, 81>& board);
|
||||
};
|
||||
|
||||
using SudokuSolverBuilder = WFC::Builder<Sudoku>
|
||||
::DefineIDs<1, 2, 3, 4, 5, 6, 7, 8, 9>
|
||||
::DefineConstrainer<decltype([](Sudoku&, size_t index, WFC::WorldValue<uint8_t> val, auto& constrainer) {
|
||||
::DefineRange<1, 10>
|
||||
::ConstrainAll<decltype([](Sudoku&, size_t index, WFC::WorldValue<uint8_t> val, auto& constrainer) constexpr {
|
||||
size_t x = index % 9;
|
||||
size_t y = index / 9;
|
||||
|
||||
// Add row constraints (same row, different columns)
|
||||
for (size_t i = 0; i < 9; ++i) {
|
||||
|
||||
for (size_t i = 0; i < 9; ++i)
|
||||
{
|
||||
// Add row constraints (same row, different columns)
|
||||
if (i != x) constrainer.Exclude(val, i + y * 9);
|
||||
}
|
||||
|
||||
// Add column constraints (same column, different rows)
|
||||
for (size_t i = 0; i < 9; ++i) {
|
||||
if (i != y) constrainer.Exclude(val,x + i * 9);
|
||||
// Add column constraints (same column, different rows)
|
||||
if (i != y) constrainer.Exclude(val, x + i * 9);
|
||||
}
|
||||
|
||||
// Add box constraints (3x3 box)
|
||||
@@ -344,7 +344,7 @@ using SudokuSolverBuilder = WFC::Builder<Sudoku>
|
||||
}
|
||||
}
|
||||
|
||||
}), 1, 2, 3, 4, 5, 6, 7, 8, 9>;
|
||||
})>;
|
||||
|
||||
using SudokuSolver = SudokuSolverBuilder::Build;
|
||||
|
||||
|
||||
@@ -58,16 +58,11 @@ using SudokuSolverCallback = SudokuSolverBuilder::SetCellCollapsedCallback<declt
|
||||
})>
|
||||
::Build;
|
||||
|
||||
Sudoku GetWorldConsteval()
|
||||
{
|
||||
return Sudoku{ "6......3.......7....7463....7.8...2.4...9...1.9...7.8....9851....6.......1......9" };
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "Running Sudoku WFC" << std::endl;
|
||||
|
||||
Sudoku sudokuWorld = GetWorldConsteval();
|
||||
Sudoku sudokuWorld = Sudoku{ "6......3.......7....7463....7.8...2.4...9...1.9...7.8....9851....6.......1......9" };
|
||||
|
||||
bool success = SudokuSolverCallback::Run(sudokuWorld, true);
|
||||
|
||||
|
||||
@@ -282,15 +282,11 @@ void testPuzzleSolving(const std::string& difficulty, const std::string& filenam
|
||||
int solvedCount = 0;
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
WFC::WFCStackAllocator allocator{};
|
||||
|
||||
for (size_t i = 0; i < puzzles.size(); ++i) {
|
||||
Sudoku& sudoku = puzzles[i];
|
||||
EXPECT_TRUE(sudoku.isValid()) << difficulty << " puzzle " << i << " is not valid";
|
||||
|
||||
auto puzzleStart = std::chrono::high_resolution_clock::now();
|
||||
SudokuSolver::Run(sudoku, allocator);
|
||||
auto puzzleEnd = std::chrono::high_resolution_clock::now();
|
||||
SudokuSolver::Run(sudoku);
|
||||
|
||||
EXPECT_TRUE(sudoku.isSolved()) << difficulty << " puzzle " << i << " was not solved. Puzzle string: " << sudoku.toString();
|
||||
|
||||
|
||||
@@ -9,4 +9,5 @@
|
||||
|
||||
#include "wfc.hpp"
|
||||
#include "worlds.hpp"
|
||||
#include "wfc_builder.hpp"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,75 +10,10 @@
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
#define WFC_USE_STACK_ALLOCATOR
|
||||
|
||||
inline void* allocate_aligned_memory(size_t alignment, size_t size) {
|
||||
#ifdef WFC_USE_STACK_ALLOCATOR
|
||||
void* ptr = nullptr;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
ptr = _aligned_malloc(size, alignment);
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#if __cplusplus >= 201703L
|
||||
ptr = std::aligned_alloc(alignment, size);
|
||||
#else
|
||||
#ifdef POSIX_MEMALIGN
|
||||
posix_memalign(&ptr, alignment, size);
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#if __cplusplus >= 201703L
|
||||
ptr = std::aligned_alloc(alignment, size);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return ptr;
|
||||
#else
|
||||
// When not using stack allocator, use standard malloc with manual alignment
|
||||
void* ptr = std::malloc(size + alignment - 1 + sizeof(void*));
|
||||
if (!ptr) return nullptr;
|
||||
|
||||
void* aligned_ptr = static_cast<char*>(ptr) + sizeof(void*) +
|
||||
(alignment - (reinterpret_cast<uintptr_t>(static_cast<char*>(ptr) + sizeof(void*)) % alignment)) % alignment;
|
||||
|
||||
// Store original pointer for free
|
||||
*(static_cast<void**>(aligned_ptr) - 1) = ptr;
|
||||
|
||||
return aligned_ptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void free_aligned_memory(void* ptr) {
|
||||
if (!ptr) return;
|
||||
|
||||
#ifdef WFC_USE_STACK_ALLOCATOR
|
||||
#ifdef _MSC_VER
|
||||
_aligned_free(ptr);
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#if __cplusplus >= 201703L
|
||||
std::free(ptr);
|
||||
#else
|
||||
#ifdef POSIX_MEMALIGN
|
||||
free(ptr);
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#if __cplusplus >= 201703L
|
||||
std::free(ptr);
|
||||
#else
|
||||
free(ptr);
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
// When not using stack allocator, free the original pointer
|
||||
void* original_ptr = *(static_cast<void**>(ptr) - 1);
|
||||
std::free(original_ptr);
|
||||
#endif
|
||||
}
|
||||
#include "wfc_utils.hpp"
|
||||
|
||||
namespace WFC {
|
||||
|
||||
#ifdef WFC_USE_STACK_ALLOCATOR
|
||||
/**
|
||||
* @brief Stack allocator specifically designed for WFC branching operations
|
||||
*
|
||||
@@ -97,16 +32,6 @@ private:
|
||||
MemoryPool(void* p, size_t s) : ptr(p), size(s), used(0) {}
|
||||
};
|
||||
|
||||
struct Block {
|
||||
void* ptr;
|
||||
size_t size;
|
||||
size_t alignment;
|
||||
size_t poolIndex; // Which pool this allocation came from
|
||||
|
||||
Block() : ptr(nullptr), size(0), alignment(0), poolIndex(0) {}
|
||||
Block(void* p, size_t s, size_t a, size_t pi) : ptr(p), size(s), alignment(a), poolIndex(pi) {}
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct allocator with initial capacity
|
||||
@@ -114,14 +39,22 @@ public:
|
||||
*/
|
||||
explicit WFCStackAllocator(size_t initialCapacity = 1024 * 1024) // 1MB default
|
||||
{
|
||||
addPool(0); // first pool is 0
|
||||
addPool(initialCapacity);
|
||||
m_currentPoolIndex = 1;
|
||||
}
|
||||
|
||||
explicit WFCStackAllocator(std::span<uint8_t> userGivenData)
|
||||
{
|
||||
m_pools.push_back(MemoryPool(userGivenData.data(), userGivenData.size()));
|
||||
m_currentPoolIndex = 0;
|
||||
}
|
||||
|
||||
~WFCStackAllocator() {
|
||||
for (auto& pool : m_pools) {
|
||||
if (pool.ptr) {
|
||||
free_aligned_memory(pool.ptr);
|
||||
}
|
||||
// first pool is not deallocated because it's either empty or comes from the user
|
||||
for (size_t i = 1; i < m_pools.size(); ++i)
|
||||
{
|
||||
delete[] static_cast<char*>(m_pools[i].ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,55 +64,31 @@ public:
|
||||
WFCStackAllocator(WFCStackAllocator&&) = delete;
|
||||
WFCStackAllocator& operator=(WFCStackAllocator&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Allocate memory from the stack
|
||||
* @param size Number of bytes to allocate
|
||||
* @param alignment Memory alignment requirement (default 8)
|
||||
* @return Pointer to allocated memory
|
||||
*/
|
||||
void* allocate(size_t size, size_t alignment = 8) {
|
||||
// Try to allocate from existing pools
|
||||
for (size_t i = 0; i < m_pools.size(); ++i) {
|
||||
auto& pool = m_pools[i];
|
||||
|
||||
// Align the current position in this pool
|
||||
size_t alignedUsed = alignUp(pool.used, alignment);
|
||||
|
||||
// Check if we have enough space in this pool
|
||||
if (alignedUsed + size <= pool.size) {
|
||||
void* ptr = static_cast<char*>(pool.ptr) + alignedUsed;
|
||||
pool.used = alignedUsed + size;
|
||||
|
||||
// Track this allocation for proper cleanup
|
||||
m_allocations.emplace_back(ptr, size, alignment, i);
|
||||
void* allocate(size_t size)
|
||||
{
|
||||
size = alignUp(size);
|
||||
|
||||
for (; m_currentPoolIndex < m_pools.size(); ++m_currentPoolIndex)
|
||||
{
|
||||
if (getCapacity() >= size)
|
||||
{
|
||||
auto& pool = m_pools[m_currentPoolIndex];
|
||||
void* ptr = static_cast<char*>(pool.ptr) + pool.used;
|
||||
pool.used += size;
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// No existing pool has enough space, add a new one
|
||||
size_t newPoolSize = std::max(m_pools.back().size * 2, size * 2); // Grow exponentially
|
||||
addPool(newPoolSize);
|
||||
|
||||
// Now allocate from the new pool (which is the last one)
|
||||
auto& newPool = m_pools.back();
|
||||
void* ptr = static_cast<char*>(newPool.ptr);
|
||||
newPool.used = size;
|
||||
|
||||
// Track this allocation for proper cleanup
|
||||
m_allocations.emplace_back(ptr, size, alignment, m_pools.size() - 1);
|
||||
|
||||
return ptr;
|
||||
return allocate(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deallocate memory (no-op as requested - memory freed when branch goes out of scope)
|
||||
* @param ptr Pointer to deallocate
|
||||
*/
|
||||
void deallocate(void*) {
|
||||
// No-op as requested - deallocation happens when branch goes out of scope
|
||||
// The stack nature ensures automatic cleanup
|
||||
}
|
||||
void deallocate(void*)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Create a stack frame marker for RAII-based cleanup
|
||||
@@ -188,40 +97,25 @@ public:
|
||||
class StackFrame {
|
||||
private:
|
||||
WFCStackAllocator& m_allocator;
|
||||
std::vector<size_t> m_savedUsed;
|
||||
size_t m_savedAllocCount;
|
||||
size_t m_poolIndex{};
|
||||
size_t m_poolUsed{};
|
||||
|
||||
public:
|
||||
StackFrame(WFCStackAllocator& allocator)
|
||||
: m_allocator(allocator)
|
||||
, m_savedAllocCount(allocator.m_allocations.size())
|
||||
, m_poolIndex(allocator.m_currentPoolIndex)
|
||||
, m_poolUsed(allocator.m_pools[m_poolIndex].used)
|
||||
{}
|
||||
|
||||
~StackFrame()
|
||||
{
|
||||
// Save the current used state of all pools
|
||||
m_savedUsed.reserve(allocator.m_pools.size());
|
||||
for (const auto& pool : allocator.m_pools) {
|
||||
m_savedUsed.push_back(pool.used);
|
||||
}
|
||||
}
|
||||
|
||||
~StackFrame() {
|
||||
// Restore the used state of all pools
|
||||
for (size_t i = 0; i < m_savedUsed.size() && i < m_allocator.m_pools.size(); ++i) {
|
||||
m_allocator.m_pools[i].used = m_savedUsed[i];
|
||||
for (size_t i = m_allocator.m_pools.size() - 1; i > m_poolIndex; --i)
|
||||
{
|
||||
m_allocator.m_pools[i].used = 0;
|
||||
}
|
||||
|
||||
// Remove any new pools that were added during this frame
|
||||
if (m_allocator.m_pools.size() > m_savedUsed.size()) {
|
||||
// Free the additional pools that were added
|
||||
for (size_t i = m_savedUsed.size(); i < m_allocator.m_pools.size(); ++i) {
|
||||
if (m_allocator.m_pools[i].ptr) {
|
||||
free_aligned_memory(m_allocator.m_pools[i].ptr);
|
||||
}
|
||||
}
|
||||
m_allocator.m_pools.resize(m_savedUsed.size());
|
||||
}
|
||||
|
||||
// Remove allocations that were made during this frame
|
||||
m_allocator.m_allocations.resize(m_savedAllocCount);
|
||||
m_allocator.m_pools[m_poolIndex].used = m_poolUsed;
|
||||
m_allocator.m_currentPoolIndex = m_poolIndex;
|
||||
}
|
||||
|
||||
// Non-copyable, movable
|
||||
@@ -234,106 +128,36 @@ public:
|
||||
/**
|
||||
* @brief Create a new stack frame for a branch
|
||||
*/
|
||||
StackFrame createFrame() {
|
||||
StackFrame createFrame()
|
||||
{
|
||||
return StackFrame(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get current memory usage
|
||||
*/
|
||||
size_t getUsed() const {
|
||||
size_t total = 0;
|
||||
for (const auto& pool : m_pools) {
|
||||
total += pool.used;
|
||||
}
|
||||
return total;
|
||||
constexpr size_t getCapacity() const
|
||||
{
|
||||
return m_pools[m_currentPoolIndex].size - m_pools[m_currentPoolIndex].used;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get total capacity
|
||||
*/
|
||||
size_t getCapacity() const {
|
||||
size_t total = 0;
|
||||
for (const auto& pool : m_pools) {
|
||||
total += pool.size;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get allocation count
|
||||
*/
|
||||
size_t getAllocationCount() const { return m_allocations.size(); }
|
||||
|
||||
/**
|
||||
* @brief Reset the allocator (useful for reusing between WFC runs)
|
||||
*/
|
||||
void reset() {
|
||||
for (auto& pool : m_pools) {
|
||||
pool.used = 0;
|
||||
}
|
||||
m_allocations.clear();
|
||||
static constexpr size_t alignUp(size_t value)
|
||||
{
|
||||
return (value + 8 - 1) & ~(8 - 1);
|
||||
}
|
||||
|
||||
private:
|
||||
void addPool(size_t size) {
|
||||
void* ptr = allocate_aligned_memory(64, size); // 64-byte alignment for cache efficiency
|
||||
constexpr void addPool(size_t size)
|
||||
{
|
||||
void* ptr = new char[size]; // Allocate bytes
|
||||
if (!ptr) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
m_pools.emplace_back(ptr, size);
|
||||
}
|
||||
|
||||
size_t alignUp(size_t value, size_t alignment) const {
|
||||
return (value + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<MemoryPool> m_pools;
|
||||
std::vector<Block> m_allocations;
|
||||
size_t m_currentPoolIndex{};
|
||||
};
|
||||
|
||||
#else // WFC_USE_STACK_ALLOCATOR not defined
|
||||
/**
|
||||
* @brief Simplified allocator using standard malloc/free
|
||||
*/
|
||||
class WFCStackAllocator {
|
||||
public:
|
||||
explicit WFCStackAllocator(size_t = 1024 * 1024) {}
|
||||
~WFCStackAllocator() = default;
|
||||
|
||||
// Non-copyable, non-movable for consistency
|
||||
WFCStackAllocator(const WFCStackAllocator&) = delete;
|
||||
WFCStackAllocator& operator=(const WFCStackAllocator&) = delete;
|
||||
WFCStackAllocator(WFCStackAllocator&&) = delete;
|
||||
WFCStackAllocator& operator=(WFCStackAllocator&&) = delete;
|
||||
|
||||
void* allocate(size_t size, size_t alignment = 8) {
|
||||
return allocate_aligned_memory(alignment, size);
|
||||
}
|
||||
|
||||
void deallocate(void* ptr) {
|
||||
free_aligned_memory(ptr);
|
||||
}
|
||||
|
||||
class StackFrame {
|
||||
public:
|
||||
StackFrame(WFCStackAllocator&) {}
|
||||
~StackFrame() = default;
|
||||
StackFrame(const StackFrame&) = delete;
|
||||
StackFrame& operator=(const StackFrame&) = delete;
|
||||
StackFrame(StackFrame&&) = default;
|
||||
StackFrame& operator=(StackFrame&&) = default;
|
||||
};
|
||||
|
||||
StackFrame createFrame() { return StackFrame(*this); }
|
||||
size_t getUsed() const { return 0; }
|
||||
size_t getCapacity() const { return 0; }
|
||||
size_t getAllocationCount() const { return 0; }
|
||||
void reset() {}
|
||||
};
|
||||
#endif // WFC_USE_STACK_ALLOCATOR
|
||||
|
||||
/**
|
||||
* @brief Custom allocator adapter for STL containers using WFCStackAllocator
|
||||
*/
|
||||
@@ -361,7 +185,11 @@ public:
|
||||
: m_allocator(other.m_allocator) {}
|
||||
|
||||
pointer allocate(size_type n) {
|
||||
return static_cast<pointer>(m_allocator->allocate(n * sizeof(T), alignof(T)));
|
||||
size_t size = n * sizeof(T);
|
||||
size_t alignment = alignof(T);
|
||||
// Ensure alignment
|
||||
size = (size + alignment - 1) & ~(alignment - 1);
|
||||
return static_cast<pointer>(m_allocator->allocate(size));
|
||||
}
|
||||
|
||||
void deallocate(pointer ptr, size_type) {
|
||||
@@ -381,16 +209,4 @@ public:
|
||||
WFCStackAllocator* m_allocator;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Stack-allocated vector using WFCStackAllocator
|
||||
*/
|
||||
template<typename T>
|
||||
using WFCVector = std::vector<T, WFCStackAllocatorAdapter<T>>;
|
||||
|
||||
/**
|
||||
* @brief Stack-allocated queue using WFCStackAllocator
|
||||
*/
|
||||
template<typename T>
|
||||
using WFCQueue = std::queue<T, std::deque<T, WFCStackAllocatorAdapter<T>>>;
|
||||
|
||||
} // namespace WFC
|
||||
|
||||
254
include/nd-wfc/wfc_bit_container.hpp
Normal file
254
include/nd-wfc/wfc_bit_container.hpp
Normal file
@@ -0,0 +1,254 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <bit>
|
||||
#include <type_traits>
|
||||
#include <iterator>
|
||||
|
||||
#include "wfc_utils.hpp"
|
||||
#include "wfc_allocator.hpp"
|
||||
#include "wfc_large_integers.hpp"
|
||||
|
||||
namespace WFC {
|
||||
|
||||
namespace detail {
|
||||
// Helper to determine the optimal storage type based on bits needed
|
||||
template<size_t Bits>
|
||||
struct OptimalStorageType {
|
||||
static constexpr size_t bits_needed = Bits == 0 ? 0 :
|
||||
(Bits <= 1) ? 1 :
|
||||
(Bits <= 2) ? 2 :
|
||||
(Bits <= 4) ? 4 :
|
||||
(Bits <= 8) ? 8 :
|
||||
(Bits <= 16) ? 16 :
|
||||
(Bits <= 32) ? 32 :
|
||||
(Bits <= 64) ? 64 :
|
||||
((Bits + 63) / 64) * 64; // Round up to multiple of 64 for >64 bits
|
||||
|
||||
using type = std::conditional_t<bits_needed <= 8, uint8_t,
|
||||
std::conditional_t<bits_needed <= 16, uint16_t,
|
||||
std::conditional_t<bits_needed <= 32, uint32_t,
|
||||
uint64_t>>>;
|
||||
};
|
||||
|
||||
// Helper for multi-element storage (>64 bits)
|
||||
template<size_t Bits>
|
||||
struct StorageArray {
|
||||
static constexpr size_t StorageBits = OptimalStorageType<Bits>::bits_needed;
|
||||
static constexpr size_t ArraySize = StorageBits > 64 ? (StorageBits / 64) : 1;
|
||||
using element_type = std::conditional_t<StorageBits <= 64, typename OptimalStorageType<Bits>::type, uint64_t>;
|
||||
using type = std::conditional_t<ArraySize == 1, element_type, LargeInteger<ArraySize>>;
|
||||
};
|
||||
|
||||
struct Empty{};
|
||||
}
|
||||
|
||||
template<size_t Bits, size_t Size = 0, typename AllocatorT = WFCStackAllocatorAdapter<typename detail::StorageArray<Bits>::type>>
|
||||
class BitContainer : private AllocatorT{
|
||||
public:
|
||||
using StorageInfo = detail::OptimalStorageType<Bits>;
|
||||
using StorageArrayInfo = detail::StorageArray<Bits>;
|
||||
using StorageType = typename StorageArrayInfo::type;
|
||||
using AllocatorType = AllocatorT;
|
||||
|
||||
static constexpr size_t BitsPerElement = Bits;
|
||||
static constexpr size_t StorageBits = StorageInfo::bits_needed;
|
||||
static constexpr bool IsResizable = (Size == 0);
|
||||
static constexpr bool IsMultiElement = (StorageBits > 64);
|
||||
static constexpr bool IsSubByte = (StorageBits < 8);
|
||||
static constexpr size_t ElementsPerByte = sizeof(StorageType) * 8 / std::max<size_t>(1u, StorageBits);
|
||||
static constexpr size_t MaxValue = (StorageType{1} << BitsPerElement) - 1;
|
||||
|
||||
using ContainerType =
|
||||
std::conditional_t<Bits == 0,
|
||||
detail::Empty,
|
||||
std::conditional_t<IsResizable,
|
||||
std::vector<StorageType, AllocatorType>,
|
||||
std::array<StorageType, Size>>>;
|
||||
|
||||
private:
|
||||
ContainerType m_container;
|
||||
|
||||
private:
|
||||
// Mask for extracting bits
|
||||
static constexpr auto get_Mask()
|
||||
{
|
||||
if constexpr (BitsPerElement == 0)
|
||||
{
|
||||
return uint64_t{0};
|
||||
}
|
||||
else if constexpr (BitsPerElement >= 64)
|
||||
{
|
||||
return ~uint64_t{0};
|
||||
}
|
||||
else
|
||||
{
|
||||
return (uint64_t{1} << BitsPerElement) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint64_t Mask = get_Mask();
|
||||
|
||||
public:
|
||||
static constexpr StorageType GetWaveMask()
|
||||
{
|
||||
return (StorageType{1} << BitsPerElement) - 1;
|
||||
}
|
||||
|
||||
static constexpr StorageType GetMask(std::span<const size_t> indices)
|
||||
{
|
||||
StorageType mask = 0;
|
||||
for (const auto& index : indices) {
|
||||
mask |= (StorageType{1} << index);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
BitContainer() = default;
|
||||
BitContainer(const AllocatorT& allocator) : AllocatorT(allocator) {};
|
||||
explicit BitContainer(size_t size, const AllocatorT& allocator) requires (IsResizable)
|
||||
: AllocatorT(allocator)
|
||||
, m_container(size, allocator)
|
||||
{};
|
||||
explicit BitContainer(size_t, const AllocatorT& allocator) requires (!IsResizable)
|
||||
: AllocatorT(allocator)
|
||||
, m_container()
|
||||
{};
|
||||
|
||||
public:
|
||||
// Size operations
|
||||
constexpr size_t size() const noexcept
|
||||
{
|
||||
if constexpr (IsResizable)
|
||||
{
|
||||
return m_container.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Size;
|
||||
}
|
||||
}
|
||||
constexpr std::span<const StorageType> data() const { return std::span<const StorageType>(m_container); }
|
||||
constexpr std::span<StorageType> data() { return std::span<StorageType>(m_container); }
|
||||
|
||||
constexpr void resize(size_t new_size) requires (IsResizable) { m_container.resize(new_size); }
|
||||
constexpr void reserve(size_t capacity) requires (IsResizable) { m_container.reserve(capacity); }
|
||||
|
||||
public: // Sub byte
|
||||
struct SubTypeAccess
|
||||
{
|
||||
constexpr SubTypeAccess(uint8_t& data, uint8_t subIndex) : Data{ data }, Shift{ StorageBits * subIndex } {};
|
||||
|
||||
constexpr uint8_t GetValue() const { return ((Data >> Shift) & Mask); }
|
||||
constexpr uint8_t SetValue(uint8_t val) { Clear(); return Data |= ((val & Mask) << Shift); }
|
||||
constexpr void Clear() { Data &= ~Mask; }
|
||||
|
||||
|
||||
constexpr SubTypeAccess& operator=(uint8_t other) { return SetValue(other); }
|
||||
constexpr operator uint8_t() const { return GetValue(); }
|
||||
|
||||
constexpr SubTypeAccess& operator&=(uint8_t other) { return SetValue(GetValue() & other); }
|
||||
constexpr SubTypeAccess& operator|=(uint8_t other) { return SetValue(GetValue() | other); }
|
||||
constexpr SubTypeAccess& operator^=(uint8_t other) { return SetValue(GetValue() ^ other); }
|
||||
constexpr SubTypeAccess& operator<<=(uint8_t other) { return SetValue(GetValue() << other); }
|
||||
constexpr SubTypeAccess& operator>>=(uint8_t other) { return SetValue(GetValue() >> other); }
|
||||
|
||||
uint8_t& Data;
|
||||
uint8_t Shift;
|
||||
};
|
||||
|
||||
constexpr const SubTypeAccess operator[](size_t index) const requires(IsSubByte) { return SubTypeAccess{data()[index / ElementsPerByte], index & ElementsPerByte }; }
|
||||
constexpr SubTypeAccess operator[](size_t index) requires(IsSubByte) { return SubTypeAccess{data()[index / ElementsPerByte], index & ElementsPerByte }; }
|
||||
|
||||
public: // default
|
||||
constexpr const StorageType& operator[](size_t index) const requires(!IsSubByte) { return data()[index]; }
|
||||
constexpr StorageType& operator[](size_t index) requires(!IsSubByte) { return data()[index]; }
|
||||
|
||||
public: // iterators
|
||||
template <bool IsConst>
|
||||
class BitIterator {
|
||||
public:
|
||||
// Iterator traits
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
using value_type = StorageType;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = std::conditional_t<IsConst, const StorageType*, StorageType*>;
|
||||
using reference = std::conditional_t<IsConst, const StorageType&, StorageType&>;
|
||||
|
||||
private:
|
||||
using ContainerType = std::conditional_t<IsConst, const BitContainer, BitContainer>;
|
||||
|
||||
ContainerType* m_container{};
|
||||
size_t m_index{};
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
constexpr BitIterator() = default;
|
||||
constexpr BitIterator(ContainerType& container, size_t index) : m_container(&container), m_index(index) {}
|
||||
|
||||
// Dereference
|
||||
constexpr reference operator*() const { return (*m_container)[m_index]; }
|
||||
constexpr pointer operator->() const { return &(*m_container)[m_index]; }
|
||||
|
||||
// Element access
|
||||
constexpr reference operator[](difference_type n) const { return (*m_container)[m_index + n]; }
|
||||
|
||||
// Increment / Decrement
|
||||
constexpr BitIterator& operator++() { ++m_index; return *this; }
|
||||
constexpr BitIterator operator++(int) { BitIterator tmp = *this; ++m_index; return tmp; }
|
||||
constexpr BitIterator& operator--() { --m_index; return *this; }
|
||||
constexpr BitIterator operator--(int) { BitIterator tmp = *this; --m_index; return tmp; }
|
||||
|
||||
// Arithmetic
|
||||
constexpr BitIterator operator+(difference_type n) const { return BitIterator(*m_container, m_index + n); }
|
||||
constexpr BitIterator operator-(difference_type n) const { return BitIterator(*m_container, m_index - n); }
|
||||
constexpr difference_type operator-(const BitIterator& other) const { return static_cast<difference_type>(m_index) - static_cast<difference_type>(other.m_index); }
|
||||
|
||||
// Assignment
|
||||
constexpr BitIterator& operator+=(difference_type n) { m_index += n; return *this; }
|
||||
constexpr BitIterator& operator-=(difference_type n) { m_index -= n; return *this; }
|
||||
|
||||
// Comparison
|
||||
constexpr bool operator==(const BitIterator& other) const { return m_index == other.m_index; }
|
||||
constexpr bool operator!=(const BitIterator& other) const { return m_index != other.m_index; }
|
||||
constexpr bool operator<(const BitIterator& other) const { return m_index < other.m_index; }
|
||||
constexpr bool operator>(const BitIterator& other) const { return m_index > other.m_index; }
|
||||
constexpr bool operator<=(const BitIterator& other) const { return m_index <= other.m_index; }
|
||||
constexpr bool operator>=(const BitIterator& other) const { return m_index >= other.m_index; }
|
||||
|
||||
// Conversion from non-const to const iterator
|
||||
constexpr operator BitIterator<true>() const {
|
||||
return BitIterator<true>(*m_container, m_index);
|
||||
}
|
||||
};
|
||||
|
||||
// Type aliases for convenience
|
||||
using ConstIterator = BitIterator<true>;
|
||||
using Iterator = BitIterator<false>;
|
||||
|
||||
constexpr Iterator begin() { return Iterator{*this, 0}; }
|
||||
constexpr Iterator end() { return Iterator{*this, size()}; }
|
||||
constexpr const ConstIterator begin() const { return ConstIterator{*this, 0}; }
|
||||
constexpr const ConstIterator end() const { return ConstIterator{*this, size()}; }
|
||||
};
|
||||
|
||||
// Free function for iterator addition
|
||||
template <size_t Bits, size_t Size = 0, typename AllocatorT = WFCStackAllocatorAdapter<typename detail::StorageArray<Bits>::type>, bool IsConst>
|
||||
BitContainer<Bits, Size, AllocatorT>::BitIterator<IsConst> operator+(
|
||||
typename BitContainer<Bits, Size, AllocatorT>::template BitIterator<IsConst>::difference_type n,
|
||||
const typename BitContainer<Bits, Size, AllocatorT>::template BitIterator<IsConst>& it) {
|
||||
return it + n;
|
||||
}
|
||||
|
||||
static_assert(BitContainer<1, 10>::ElementsPerByte == 8);
|
||||
static_assert(BitContainer<2, 10>::ElementsPerByte == 4);
|
||||
static_assert(BitContainer<4, 10>::ElementsPerByte == 2);
|
||||
static_assert(BitContainer<8, 10>::ElementsPerByte == 1);
|
||||
|
||||
|
||||
} // namespace WFC
|
||||
98
include/nd-wfc/wfc_builder.hpp
Normal file
98
include/nd-wfc/wfc_builder.hpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
namespace WFC {
|
||||
|
||||
#include "wfc_utils.hpp"
|
||||
#include "wfc_variable_map.hpp"
|
||||
#include "wfc_constrainer.hpp"
|
||||
#include "wfc_callbacks.hpp"
|
||||
#include "wfc_random.hpp"
|
||||
#include "wfc_weights.hpp"
|
||||
#include "wfc.hpp"
|
||||
|
||||
/**
|
||||
* @brief Builder class for creating WFC instances
|
||||
*/
|
||||
template<
|
||||
typename WorldT,
|
||||
typename VarT = typename WorldT::ValueType,
|
||||
typename VariableIDMapT = VariableIDMap<VarT>,
|
||||
typename ConstrainerFunctionMapT = ConstrainerFunctionMap<void*>,
|
||||
typename CallbacksT = Callbacks<WorldT>,
|
||||
typename RandomSelectorT = DefaultRandomSelector<VarT>,
|
||||
typename WeightsMapT = WeightsMap<VariableIDMapT>,
|
||||
typename SelectedValueT = void>
|
||||
class Builder {
|
||||
public:
|
||||
using WorldSizeT = decltype(WorldT{}.size());
|
||||
constexpr static WorldSizeT WorldSize = HasConstexprSize<WorldT> ? WorldT{}.size() : 0;
|
||||
|
||||
using WaveType = Wave<VariableIDMapT, WeightsMapT, WorldSize>;
|
||||
using PropagationQueueType = WFCQueue<WorldSize, WorldSizeT>;
|
||||
using ConstrainerType = Constrainer<WaveType, PropagationQueueType>;
|
||||
|
||||
|
||||
template <VarT ... Values>
|
||||
using DefineIDs = Builder<WorldT, VarT, VariableIDMap<VarT, Values...>, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, WeightsMapT, VariableIDMap<VarT, Values...>>;
|
||||
|
||||
template <size_t RangeStart, size_t RangeEnd>
|
||||
using DefineRange = Builder<WorldT, VarT, VariableIDRange<VarT, RangeStart, RangeEnd>, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, WeightsMapT, VariableIDRange<VarT, RangeStart, RangeEnd>>;
|
||||
|
||||
template <size_t RangeEnd>
|
||||
using DefineRange0 = Builder<WorldT, VarT, VariableIDRange<VarT, 0, RangeEnd>, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, WeightsMapT, VariableIDRange<VarT, 0, RangeEnd>>;
|
||||
|
||||
|
||||
template <VarT ... Values>
|
||||
using Variable = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, WeightsMapT, VariableIDMap<VarT, Values...>>;
|
||||
|
||||
template <size_t RangeStart, size_t RangeEnd>
|
||||
using VariableRange = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, WeightsMapT, VariableIDRange<VarT, RangeStart, RangeEnd>>;
|
||||
|
||||
|
||||
using EmptyConstrainerFunctionT = EmptyConstrainerFunction<WorldT, WorldSizeT, VarT, ConstrainerType>;
|
||||
|
||||
template <typename ConstrainerFunctionT>
|
||||
requires ConstrainerFunction<ConstrainerFunctionT, WorldT, VarT, WaveType, PropagationQueueType>
|
||||
using Constrain = Builder<WorldT, VarT, VariableIDMapT,
|
||||
MergedConstrainerFunctionMap<
|
||||
VariableIDMapT,
|
||||
ConstrainerFunctionMapT,
|
||||
ConstrainerFunctionT,
|
||||
SelectedValueT,
|
||||
EmptyConstrainerFunctionT
|
||||
>, CallbacksT, RandomSelectorT, WeightsMapT, SelectedValueT
|
||||
>;
|
||||
|
||||
template <typename ConstrainerFunctionT>
|
||||
requires ConstrainerFunction<ConstrainerFunctionT, WorldT, VarT, WaveType, PropagationQueueType>
|
||||
using ConstrainAll = Builder<WorldT, VarT, VariableIDMapT,
|
||||
MergedConstrainerFunctionMap<
|
||||
VariableIDMapT,
|
||||
ConstrainerFunctionMapT,
|
||||
ConstrainerFunctionT,
|
||||
VariableIDMapT,
|
||||
EmptyConstrainerFunctionT
|
||||
>, CallbacksT, RandomSelectorT, WeightsMapT
|
||||
>;
|
||||
|
||||
|
||||
template <typename NewCellCollapsedCallbackT>
|
||||
using SetCellCollapsedCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetCellCollapsedCallbackT<NewCellCollapsedCallbackT>, RandomSelectorT, WeightsMapT>;
|
||||
template <typename NewContradictionCallbackT>
|
||||
using SetContradictionCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetContradictionCallbackT<NewContradictionCallbackT>, RandomSelectorT, WeightsMapT>;
|
||||
template <typename NewBranchCallbackT>
|
||||
using SetBranchCallback = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, typename CallbacksT::template SetBranchCallbackT<NewBranchCallbackT>, RandomSelectorT, WeightsMapT>;
|
||||
|
||||
|
||||
template <typename NewRandomSelectorT>
|
||||
using SetRandomSelector = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, NewRandomSelectorT, WeightsMapT>;
|
||||
|
||||
|
||||
template <EPrecision Precision>
|
||||
using SetWeights = Builder<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, typename WeightsMapT::template Merge<PrecisionEntry<SelectedValueT, static_cast<uint8_t>(Precision)>>, SelectedValueT>;
|
||||
|
||||
|
||||
using Build = WFC<WorldT, VarT, VariableIDMapT, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT, WeightsMapT>;
|
||||
};
|
||||
|
||||
}
|
||||
47
include/nd-wfc/wfc_callbacks.hpp
Normal file
47
include/nd-wfc/wfc_callbacks.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
namespace WFC {
|
||||
|
||||
/**
|
||||
* @brief Empty callback function
|
||||
* @param WorldT The world type
|
||||
*/
|
||||
template <typename WorldT>
|
||||
struct EmptyCallback
|
||||
{
|
||||
void operator()(WorldT&) const {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Callback struct
|
||||
* @param WorldT The world type
|
||||
* @param AllCellsCollapsedCallbackT The all cells collapsed callback type
|
||||
* @param CellCollapsedCallbackT The cell collapsed callback type
|
||||
* @param ContradictionCallbackT The contradiction callback type
|
||||
* @param BranchCallbackT The branch callback type
|
||||
*/
|
||||
template <typename WorldT,
|
||||
typename CellCollapsedCallbackT = EmptyCallback<WorldT>,
|
||||
typename ContradictionCallbackT = EmptyCallback<WorldT>,
|
||||
typename BranchCallbackT = EmptyCallback<WorldT>
|
||||
>
|
||||
struct Callbacks
|
||||
{
|
||||
using CellCollapsedCallback = CellCollapsedCallbackT;
|
||||
using ContradictionCallback = ContradictionCallbackT;
|
||||
using BranchCallback = BranchCallbackT;
|
||||
|
||||
template <typename NewCellCollapsedCallbackT>
|
||||
using SetCellCollapsedCallbackT = Callbacks<WorldT, NewCellCollapsedCallbackT, ContradictionCallbackT, BranchCallbackT>;
|
||||
template <typename NewContradictionCallbackT>
|
||||
using SetContradictionCallbackT = Callbacks<WorldT, CellCollapsedCallbackT, NewContradictionCallbackT, BranchCallbackT>;
|
||||
template <typename NewBranchCallbackT>
|
||||
using SetBranchCallbackT = Callbacks<WorldT, CellCollapsedCallbackT, ContradictionCallbackT, NewBranchCallbackT>;
|
||||
|
||||
static consteval bool HasCellCollapsedCallback() { return !std::is_same_v<CellCollapsedCallbackT, EmptyCallback<WorldT>>; }
|
||||
static consteval bool HasContradictionCallback() { return !std::is_same_v<ContradictionCallbackT, EmptyCallback<WorldT>>; }
|
||||
static consteval bool HasBranchCallback() { return !std::is_same_v<BranchCallbackT, EmptyCallback<WorldT>>; }
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
133
include/nd-wfc/wfc_constrainer.hpp
Normal file
133
include/nd-wfc/wfc_constrainer.hpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include "wfc_variable_map.hpp"
|
||||
#include "wfc_queue.hpp"
|
||||
|
||||
namespace WFC {
|
||||
|
||||
template <typename WorldT, typename WorldSizeT, typename VarT, typename ConstainerType>
|
||||
struct EmptyConstrainerFunction
|
||||
{
|
||||
void operator()(WorldT&, WorldSizeT, WorldValue<VarT>, ConstainerType&) const {}
|
||||
};
|
||||
|
||||
template <typename ... ConstrainerFunctions>
|
||||
struct ConstrainerFunctionMap {
|
||||
public:
|
||||
static consteval size_t size() { return sizeof...(ConstrainerFunctions); }
|
||||
|
||||
using TupleType = std::tuple<ConstrainerFunctions...>;
|
||||
|
||||
template <typename ConstrainerFunctionPtrT>
|
||||
static ConstrainerFunctionPtrT GetFunction(size_t index)
|
||||
{
|
||||
static_assert((std::is_empty_v<ConstrainerFunctions> && ...), "Lambdas must not have any captures");
|
||||
static ConstrainerFunctionPtrT functions[] = {
|
||||
static_cast<ConstrainerFunctionPtrT>(ConstrainerFunctions{}) ...
|
||||
};
|
||||
return functions[index];
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to select the correct constrainer function based on the index and the value
|
||||
template<std::size_t I,
|
||||
typename VariableIDMapT,
|
||||
typename ConstrainerFunctionMapT,
|
||||
typename NewConstrainerFunctionT,
|
||||
typename SelectedIDsVariableIDMapT,
|
||||
typename EmptyFunctionT>
|
||||
using MergedConstrainerElementSelector =
|
||||
std::conditional_t<SelectedIDsVariableIDMapT::template HasValue<VariableIDMapT::GetValue(I)>(), // if the value is in the selected IDs
|
||||
NewConstrainerFunctionT,
|
||||
std::conditional_t<(I < ConstrainerFunctionMapT::size()), // if the index is within the size of the tuple
|
||||
std::tuple_element_t<std::min(I, ConstrainerFunctionMapT::size() - 1), typename ConstrainerFunctionMapT::TupleType>,
|
||||
EmptyFunctionT
|
||||
>
|
||||
>;
|
||||
|
||||
// Helper to make a merged constrainer function map
|
||||
template<typename VariableIDMapT,
|
||||
typename ConstrainerFunctionMapT,
|
||||
typename NewConstrainerFunctionT,
|
||||
typename SelectedIDsVariableIDMapT,
|
||||
typename EmptyFunctionT,
|
||||
std::size_t... Is>
|
||||
auto MakeMergedConstrainerIDMap(std::index_sequence<Is...>,VariableIDMapT*, ConstrainerFunctionMapT*, NewConstrainerFunctionT*, SelectedIDsVariableIDMapT*, EmptyFunctionT*)
|
||||
-> ConstrainerFunctionMap<MergedConstrainerElementSelector<Is, VariableIDMapT, ConstrainerFunctionMapT, NewConstrainerFunctionT, SelectedIDsVariableIDMapT, EmptyFunctionT>...>;
|
||||
|
||||
// Main alias for the merged constrainer function map
|
||||
template<typename VariableIDMapT,
|
||||
typename ConstrainerFunctionMapT,
|
||||
typename NewConstrainerFunctionT,
|
||||
typename SelectedIDsVariableIDMapT,
|
||||
typename EmptyFunctionT>
|
||||
using MergedConstrainerFunctionMap = decltype(
|
||||
MakeMergedConstrainerIDMap(std::make_index_sequence<VariableIDMapT::size()>{}, (VariableIDMapT*)nullptr, (ConstrainerFunctionMapT*)nullptr, (NewConstrainerFunctionT*)nullptr, (SelectedIDsVariableIDMapT*)nullptr, (EmptyFunctionT*)nullptr)
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Constrainer class used in constraint functions to limit possible values for other cells
|
||||
*/
|
||||
template <typename WaveT, typename PropagationQueueT>
|
||||
class Constrainer {
|
||||
public:
|
||||
using IDMapT = typename WaveT::IDMapT;
|
||||
using BitContainerT = typename WaveT::BitContainerT;
|
||||
using MaskType = typename BitContainerT::StorageType;
|
||||
|
||||
public:
|
||||
Constrainer(WaveT& wave, PropagationQueueT& propagationQueue)
|
||||
: m_wave(wave)
|
||||
, m_propagationQueue(propagationQueue)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Constrain a cell to exclude specific values
|
||||
* @param cellId The ID of the cell to constrain
|
||||
* @param forbiddenValues The set of forbidden values for this cell
|
||||
*/
|
||||
template <typename IDMapT::Type ... ExcludedValues>
|
||||
void Exclude(size_t cellId) {
|
||||
static_assert(sizeof...(ExcludedValues) > 0, "At least one excluded value must be provided");
|
||||
auto indices = IDMapT::template ValuesToIndices<ExcludedValues...>();
|
||||
ApplyMask(cellId, ~BitContainerT::GetMask(indices));
|
||||
}
|
||||
|
||||
void Exclude(WorldValue<typename IDMapT::Type> value, size_t cellId) {
|
||||
ApplyMask(cellId, ~(1 << value.InternalIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constrain a cell to only allow one specific value
|
||||
* @param cellId The ID of the cell to constrain
|
||||
* @param value The only allowed value for this cell
|
||||
*/
|
||||
template <typename IDMapT::Type ... AllowedValues>
|
||||
void Only(size_t cellId) {
|
||||
static_assert(sizeof...(AllowedValues) > 0, "At least one allowed value must be provided");
|
||||
auto indices = IDMapT::template ValuesToIndices<AllowedValues...>();
|
||||
ApplyMask(cellId, BitContainerT::GetMask(indices));
|
||||
}
|
||||
|
||||
void Only(WorldValue<typename IDMapT::Type> value, size_t cellId) {
|
||||
ApplyMask(cellId, 1 << value.InternalIndex);
|
||||
}
|
||||
|
||||
private:
|
||||
void ApplyMask(size_t cellId, MaskType mask) {
|
||||
bool wasCollapsed = m_wave.IsCollapsed(cellId);
|
||||
|
||||
m_wave.Collapse(cellId, mask);
|
||||
|
||||
bool collapsed = m_wave.IsCollapsed(cellId);
|
||||
if (!wasCollapsed && collapsed) {
|
||||
m_propagationQueue.push(cellId);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
WaveT& m_wave;
|
||||
PropagationQueueT& m_propagationQueue;
|
||||
};
|
||||
|
||||
}
|
||||
517
include/nd-wfc/wfc_large_integers.hpp
Normal file
517
include/nd-wfc/wfc_large_integers.hpp
Normal file
@@ -0,0 +1,517 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include <stdexcept>
|
||||
|
||||
// Detect __uint128_t support
|
||||
#if (defined(__SIZEOF_INT128__) || defined(__INTEL_COMPILER) || (defined(__GNUC__) && __GNUC__ >= 4)) && !defined(_MSC_VER)
|
||||
#define WFC_HAS_UINT128 1
|
||||
#else
|
||||
#define WFC_HAS_UINT128 0
|
||||
#endif
|
||||
|
||||
namespace WFC {
|
||||
|
||||
template <size_t Size>
|
||||
struct LargeInteger
|
||||
{
|
||||
static_assert(Size > 0, "Size must be greater than 0");
|
||||
|
||||
std::array<uint64_t, Size> m_data;
|
||||
|
||||
// Constructors
|
||||
constexpr LargeInteger() = default;
|
||||
constexpr LargeInteger(const LargeInteger&) = default;
|
||||
constexpr LargeInteger(LargeInteger&&) = default;
|
||||
constexpr LargeInteger& operator=(const LargeInteger&) = default;
|
||||
constexpr LargeInteger& operator=(LargeInteger&&) = default;
|
||||
|
||||
// Constructor from uint64_t (for small values)
|
||||
template <typename T, typename = std::enable_if_t<std::is_integral_v<T> && std::is_unsigned_v<T>>>
|
||||
constexpr explicit LargeInteger(T value) {
|
||||
m_data.fill(0);
|
||||
if constexpr (sizeof(T) <= sizeof(uint64_t)) {
|
||||
m_data[0] = static_cast<uint64_t>(value);
|
||||
} else {
|
||||
// Handle larger types if needed
|
||||
static_assert(sizeof(T) <= sizeof(uint64_t), "Type too large for LargeInteger");
|
||||
}
|
||||
}
|
||||
|
||||
// Access operators
|
||||
constexpr uint64_t& operator[](size_t index) { return m_data[index]; }
|
||||
constexpr const uint64_t& operator[](size_t index) const { return m_data[index]; }
|
||||
|
||||
// Helper function to get the larger size type
|
||||
template <size_t OtherSize>
|
||||
using LargerType = LargeInteger<std::max(Size, OtherSize)>;
|
||||
|
||||
// Helper function to promote operands to the same size
|
||||
template <size_t OtherSize>
|
||||
constexpr auto promote(const LargeInteger<OtherSize>& other) const {
|
||||
constexpr size_t ResultSize = std::max(Size, OtherSize);
|
||||
LargeInteger<ResultSize> lhs_promoted{};
|
||||
LargeInteger<ResultSize> rhs_promoted{};
|
||||
|
||||
// Copy data, padding with zeros
|
||||
for (size_t i = 0; i < Size; ++i) {
|
||||
lhs_promoted[i] = m_data[i];
|
||||
}
|
||||
for (size_t i = 0; i < OtherSize; ++i) {
|
||||
rhs_promoted[i] = other[i];
|
||||
}
|
||||
|
||||
return std::make_pair(lhs_promoted, rhs_promoted);
|
||||
}
|
||||
|
||||
// Arithmetic operators
|
||||
template <size_t OtherSize>
|
||||
constexpr LargerType<OtherSize> operator+(const LargeInteger<OtherSize>& other) const {
|
||||
auto [lhs, rhs] = promote(other);
|
||||
constexpr size_t ResultSize = std::max(Size, OtherSize);
|
||||
LargeInteger<ResultSize> result{};
|
||||
|
||||
uint64_t carry = 0;
|
||||
for (size_t i = 0; i < ResultSize; ++i) {
|
||||
uint64_t sum = lhs[i] + rhs[i] + carry;
|
||||
result[i] = sum;
|
||||
carry = (sum < lhs[i] || (carry && sum == lhs[i])) ? 1 : 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargeInteger& operator+=(const LargeInteger<OtherSize>& other) {
|
||||
*this = *this + other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargerType<OtherSize> operator-(const LargeInteger<OtherSize>& other) const {
|
||||
auto [lhs, rhs] = promote(other);
|
||||
constexpr size_t ResultSize = std::max(Size, OtherSize);
|
||||
LargeInteger<ResultSize> result{};
|
||||
|
||||
uint64_t borrow = 0;
|
||||
for (size_t i = 0; i < ResultSize; ++i) {
|
||||
uint64_t diff = lhs[i] - rhs[i] - borrow;
|
||||
result[i] = diff;
|
||||
borrow = (lhs[i] < rhs[i] + borrow) ? 1 : 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargeInteger& operator-=(const LargeInteger<OtherSize>& other) {
|
||||
*this = *this - other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargerType<OtherSize> operator*(const LargeInteger<OtherSize>& other) const {
|
||||
#if WFC_HAS_UINT128
|
||||
auto [lhs, rhs] = promote(other);
|
||||
constexpr size_t ResultSize = std::max(Size, OtherSize);
|
||||
LargeInteger<ResultSize * 2> result{}; // Multiplication can double the size
|
||||
|
||||
for (size_t i = 0; i < ResultSize; ++i) {
|
||||
uint64_t carry = 0;
|
||||
for (size_t j = 0; j < ResultSize; ++j) {
|
||||
__uint128_t product = static_cast<__uint128_t>(lhs[i]) * rhs[j] + result[i + j] + carry;
|
||||
result[i + j] = static_cast<uint64_t>(product);
|
||||
carry = product >> 64;
|
||||
}
|
||||
size_t k = i + ResultSize;
|
||||
while (carry && k < ResultSize * 2) {
|
||||
__uint128_t sum = result[k] + carry;
|
||||
result[k] = static_cast<uint64_t>(sum);
|
||||
carry = sum >> 64;
|
||||
++k;
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate to the larger of the original sizes
|
||||
LargeInteger<ResultSize> final_result{};
|
||||
for (size_t i = 0; i < ResultSize; ++i) {
|
||||
final_result[i] = result[i];
|
||||
}
|
||||
return final_result;
|
||||
#else
|
||||
throw std::runtime_error("LargeInteger multiplication requires __uint128_t support, which is not available on this compiler/platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargeInteger& operator*=(const LargeInteger<OtherSize>& other) {
|
||||
*this = *this * other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Division and modulo (simplified implementation)
|
||||
template <size_t OtherSize>
|
||||
constexpr LargerType<OtherSize> operator/(const LargeInteger<OtherSize>& other) const {
|
||||
// Simplified division - assumes other is not zero and result fits
|
||||
auto [lhs, rhs] = promote(other);
|
||||
constexpr size_t ResultSize = std::max(Size, OtherSize);
|
||||
LargeInteger<ResultSize> result{};
|
||||
|
||||
// This is a very basic division implementation
|
||||
// For a full implementation, you'd need proper long division
|
||||
LargeInteger<ResultSize> temp = lhs;
|
||||
while (temp >= rhs) {
|
||||
temp = temp - rhs;
|
||||
result = result + LargeInteger<ResultSize>{1};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargerType<OtherSize> operator%(const LargeInteger<OtherSize>& other) const {
|
||||
auto [lhs, rhs] = promote(other);
|
||||
constexpr size_t ResultSize = std::max(Size, OtherSize);
|
||||
LargeInteger<ResultSize> temp = lhs;
|
||||
while (temp >= rhs) {
|
||||
temp = temp - rhs;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
// Unary operators
|
||||
constexpr LargeInteger operator-() const {
|
||||
LargeInteger result{};
|
||||
for (size_t i = 0; i < Size; ++i) {
|
||||
result[i] = ~m_data[i] + 1; // Two's complement
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr LargeInteger operator~() const {
|
||||
LargeInteger result{};
|
||||
for (size_t i = 0; i < Size; ++i) {
|
||||
result[i] = ~m_data[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Bit operations
|
||||
template <size_t OtherSize>
|
||||
constexpr LargerType<OtherSize> operator&(const LargeInteger<OtherSize>& other) const {
|
||||
auto [lhs, rhs] = promote(other);
|
||||
return lhs.bitwise_op(rhs, std::bit_and<uint64_t>{});
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargeInteger& operator&=(const LargeInteger<OtherSize>& other) {
|
||||
*this = *this & other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargerType<OtherSize> operator|(const LargeInteger<OtherSize>& other) const {
|
||||
auto [lhs, rhs] = promote(other);
|
||||
return lhs.bitwise_op(rhs, std::bit_or<uint64_t>{});
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargeInteger& operator|=(const LargeInteger<OtherSize>& other) {
|
||||
*this = *this | other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargerType<OtherSize> operator^(const LargeInteger<OtherSize>& other) const {
|
||||
auto [lhs, rhs] = promote(other);
|
||||
return lhs.bitwise_op(rhs, std::bit_xor<uint64_t>{});
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargeInteger& operator^=(const LargeInteger<OtherSize>& other) {
|
||||
*this = *this ^ other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargerType<OtherSize> operator<<(size_t shift) const {
|
||||
constexpr size_t ResultSize = std::max(Size, OtherSize);
|
||||
LargeInteger<ResultSize> result = *this;
|
||||
|
||||
size_t word_shift = shift / 64;
|
||||
size_t bit_shift = shift % 64;
|
||||
|
||||
if (word_shift >= ResultSize) {
|
||||
result.m_data.fill(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Shift words
|
||||
for (size_t i = ResultSize - 1; i >= word_shift; --i) {
|
||||
result[i] = result[i - word_shift];
|
||||
}
|
||||
for (size_t i = 0; i < word_shift; ++i) {
|
||||
result[i] = 0;
|
||||
}
|
||||
|
||||
// Shift bits
|
||||
if (bit_shift > 0) {
|
||||
uint64_t carry = 0;
|
||||
for (size_t i = word_shift; i < ResultSize; ++i) {
|
||||
uint64_t new_carry = result[i] >> (64 - bit_shift);
|
||||
result[i] = (result[i] << bit_shift) | carry;
|
||||
carry = new_carry;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargeInteger& operator<<=(size_t shift) {
|
||||
*this = *this << shift;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargerType<OtherSize> operator>>(size_t shift) const {
|
||||
constexpr size_t ResultSize = std::max(Size, OtherSize);
|
||||
LargeInteger<ResultSize> result = *this;
|
||||
|
||||
size_t word_shift = shift / 64;
|
||||
size_t bit_shift = shift % 64;
|
||||
|
||||
if (word_shift >= ResultSize) {
|
||||
result.m_data.fill(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Shift words
|
||||
for (size_t i = 0; i < ResultSize - word_shift; ++i) {
|
||||
result[i] = result[i + word_shift];
|
||||
}
|
||||
for (size_t i = ResultSize - word_shift; i < ResultSize; ++i) {
|
||||
result[i] = 0;
|
||||
}
|
||||
|
||||
// Shift bits
|
||||
if (bit_shift > 0) {
|
||||
uint64_t carry = 0;
|
||||
for (size_t i = ResultSize - word_shift - 1; i < ResultSize; --i) {
|
||||
uint64_t new_carry = result[i] << (64 - bit_shift);
|
||||
result[i] = (result[i] >> bit_shift) | carry;
|
||||
carry = new_carry;
|
||||
if (i == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargeInteger& operator>>=(size_t shift) {
|
||||
*this = *this >> shift;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Comparison operators
|
||||
template <size_t OtherSize>
|
||||
constexpr bool operator==(const LargeInteger<OtherSize>& other) const {
|
||||
auto [lhs, rhs] = promote(other);
|
||||
return lhs.m_data == rhs.m_data;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr bool operator!=(const LargeInteger<OtherSize>& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr bool operator<(const LargeInteger<OtherSize>& other) const {
|
||||
auto [lhs, rhs] = promote(other);
|
||||
for (size_t i = lhs.m_data.size(); i > 0; --i) {
|
||||
if (lhs.m_data[i-1] != rhs.m_data[i-1]) {
|
||||
return lhs.m_data[i-1] < rhs.m_data[i-1];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr bool operator<=(const LargeInteger<OtherSize>& other) const {
|
||||
return *this < other || *this == other;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr bool operator>(const LargeInteger<OtherSize>& other) const {
|
||||
return other < *this;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr bool operator>=(const LargeInteger<OtherSize>& other) const {
|
||||
return other <= *this;
|
||||
}
|
||||
|
||||
// std::bit library functions
|
||||
constexpr int countl_zero() const {
|
||||
for (size_t i = Size; i > 0; --i) {
|
||||
if (m_data[i-1] != 0) {
|
||||
return std::countl_zero(m_data[i-1]) + (Size - i) * 64;
|
||||
}
|
||||
}
|
||||
return Size * 64;
|
||||
}
|
||||
|
||||
constexpr int countl_one() const {
|
||||
for (size_t i = Size; i > 0; --i) {
|
||||
if (m_data[i-1] != std::numeric_limits<uint64_t>::max()) {
|
||||
return std::countl_one(m_data[i-1]) + (Size - i) * 64;
|
||||
}
|
||||
}
|
||||
return Size * 64;
|
||||
}
|
||||
|
||||
constexpr int countr_zero() const {
|
||||
for (size_t i = 0; i < Size; ++i) {
|
||||
if (m_data[i] != 0) {
|
||||
return std::countr_zero(m_data[i]) + i * 64;
|
||||
}
|
||||
}
|
||||
return Size * 64;
|
||||
}
|
||||
|
||||
constexpr int countr_one() const {
|
||||
for (size_t i = 0; i < Size; ++i) {
|
||||
if (m_data[i] != std::numeric_limits<uint64_t>::max()) {
|
||||
return std::countr_one(m_data[i]) + i * 64;
|
||||
}
|
||||
}
|
||||
return Size * 64;
|
||||
}
|
||||
|
||||
constexpr int popcount() const {
|
||||
int count = 0;
|
||||
for (size_t i = 0; i < Size; ++i) {
|
||||
count += std::popcount(m_data[i]);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargerType<OtherSize> rotl(size_t shift) const {
|
||||
shift %= (Size * 64);
|
||||
return (*this << shift) | (*this >> ((Size * 64) - shift));
|
||||
}
|
||||
|
||||
template <size_t OtherSize>
|
||||
constexpr LargerType<OtherSize> rotr(size_t shift) const {
|
||||
shift %= (Size * 64);
|
||||
return (*this >> shift) | (*this << ((Size * 64) - shift));
|
||||
}
|
||||
|
||||
constexpr bool has_single_bit() const {
|
||||
return popcount() == 1;
|
||||
}
|
||||
|
||||
constexpr LargeInteger bit_ceil() const {
|
||||
if (*this == LargeInteger{0}) return LargeInteger{1};
|
||||
|
||||
LargeInteger result = *this;
|
||||
result -= LargeInteger{1};
|
||||
result |= result >> 1;
|
||||
result |= result >> 2;
|
||||
result |= result >> 4;
|
||||
result |= result >> 8;
|
||||
result |= result >> 16;
|
||||
result |= result >> 32;
|
||||
|
||||
// Handle multi-word case
|
||||
for (size_t i = 1; i < Size; ++i) {
|
||||
if (result[i] != 0) {
|
||||
// Find the highest set bit in the higher words
|
||||
size_t highest_word = Size - 1;
|
||||
for (size_t j = Size - 1; j > 0; --j) {
|
||||
if (result[j] != 0) {
|
||||
highest_word = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Set all lower words to 0 and the highest word to the power of 2
|
||||
for (size_t j = 0; j < highest_word; ++j) {
|
||||
result[j] = 0;
|
||||
}
|
||||
result[highest_word] = uint64_t(1) << (63 - std::countl_zero(result[highest_word]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result += LargeInteger{1};
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr LargeInteger bit_floor() const {
|
||||
if (*this == LargeInteger{0}) return LargeInteger{0};
|
||||
|
||||
LargeInteger result = *this;
|
||||
result |= result >> 1;
|
||||
result |= result >> 2;
|
||||
result |= result >> 4;
|
||||
result |= result >> 8;
|
||||
result |= result >> 16;
|
||||
result |= result >> 32;
|
||||
|
||||
// Handle multi-word case
|
||||
for (size_t i = 1; i < Size; ++i) {
|
||||
if (result[i] != 0) {
|
||||
size_t highest_word = Size - 1;
|
||||
for (size_t j = Size - 1; j > 0; --j) {
|
||||
if (result[j] != 0) {
|
||||
highest_word = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (size_t j = 0; j < highest_word; ++j) {
|
||||
result[j] = 0;
|
||||
}
|
||||
result[highest_word] = uint64_t(1) << (63 - std::countl_zero(result[highest_word]));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Single word case
|
||||
result = LargeInteger{uint64_t(1) << (63 - std::countl_zero(result[0]))};
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr int bit_width() const {
|
||||
if (*this == LargeInteger{0}) return 0;
|
||||
|
||||
for (size_t i = Size; i > 0; --i) {
|
||||
if (m_data[i-1] != 0) {
|
||||
return (i - 1) * 64 + 64 - std::countl_zero(m_data[i-1]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// Helper function for bitwise operations
|
||||
template <typename Op>
|
||||
constexpr LargeInteger bitwise_op(const LargeInteger& other, Op op) const {
|
||||
LargeInteger result{};
|
||||
for (size_t i = 0; i < Size; ++i) {
|
||||
result[i] = op(m_data[i], other[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// Deduction guide for constructor from integral types
|
||||
template <typename T>
|
||||
LargeInteger(T) -> LargeInteger<1>;
|
||||
|
||||
} // namespace WFC
|
||||
97
include/nd-wfc/wfc_queue.hpp
Normal file
97
include/nd-wfc/wfc_queue.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <concepts>
|
||||
#include <span>
|
||||
#include <algorithm>
|
||||
|
||||
#include "wfc_utils.hpp"
|
||||
|
||||
namespace WFC
|
||||
{
|
||||
|
||||
template <size_t Size = 0, typename StorageType = size_t>
|
||||
class WFCQueue {
|
||||
public:
|
||||
using ContainerType = std::conditional_t<Size == 0, std::vector<StorageType>, std::array<StorageType, Size>>;
|
||||
|
||||
public:
|
||||
WFCQueue() = default;
|
||||
WFCQueue(const WFCQueue&) = delete;
|
||||
WFCQueue(WFCQueue&&) = delete;
|
||||
WFCQueue& operator=(const WFCQueue&) = delete;
|
||||
WFCQueue& operator=(WFCQueue&&) = delete;
|
||||
|
||||
constexpr WFCQueue(size_t size)
|
||||
{
|
||||
if constexpr (Size == 0)
|
||||
{
|
||||
m_container.resize(size);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr std::span<const StorageType> data() const { return std::span<const StorageType>(m_container.data(), Size); }
|
||||
constexpr std::span<StorageType> data() { return std::span<StorageType>(m_container.data(), Size); }
|
||||
|
||||
constexpr std::span<const StorageType> FilledData() const { return std::span<const StorageType>(m_container.data() + m_front, m_back - m_front); }
|
||||
constexpr std::span<StorageType> FilledData() { return std::span<StorageType>(m_container.data() + m_front, m_back - m_front); }
|
||||
|
||||
constexpr size_t size() const { return m_container.size(); }
|
||||
|
||||
public:
|
||||
constexpr bool empty() const { return m_front == m_back; }
|
||||
constexpr bool full() const { return m_back == size(); }
|
||||
constexpr bool has(StorageType value) const { return std::find(m_container.begin(), m_container.begin() + m_back, value) != m_container.begin() + m_back; }
|
||||
|
||||
public:
|
||||
constexpr void push(const StorageType &value)
|
||||
{
|
||||
constexpr_assert(!full());
|
||||
constexpr_assert(!has(value));
|
||||
|
||||
m_container[m_back++] = value;
|
||||
}
|
||||
|
||||
constexpr StorageType pop()
|
||||
{
|
||||
constexpr_assert(!empty());
|
||||
|
||||
return m_container[m_front++];
|
||||
}
|
||||
|
||||
public:
|
||||
struct BranchPoint
|
||||
{
|
||||
constexpr BranchPoint(WFCQueue<Size, StorageType>& queue)
|
||||
: m_queue(queue)
|
||||
, m_front(queue.m_front)
|
||||
, m_back(queue.m_back)
|
||||
{}
|
||||
|
||||
constexpr ~BranchPoint()
|
||||
{
|
||||
m_queue.m_front = m_front;
|
||||
m_queue.m_back = m_back;
|
||||
}
|
||||
|
||||
WFCQueue<Size, StorageType>& m_queue;
|
||||
size_t m_front;
|
||||
size_t m_back;
|
||||
};
|
||||
|
||||
public:
|
||||
constexpr BranchPoint createBranchPoint()
|
||||
{
|
||||
return BranchPoint(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
ContainerType m_container{};
|
||||
size_t m_front = 0;
|
||||
size_t m_back = 0;
|
||||
};
|
||||
|
||||
} // namespace WFC
|
||||
41
include/nd-wfc/wfc_random.hpp
Normal file
41
include/nd-wfc/wfc_random.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
namespace WFC {
|
||||
|
||||
/**
|
||||
* @brief Default constexpr random selector using a simple seed-based algorithm
|
||||
* This provides a compile-time random selection that maintains state between calls
|
||||
*/
|
||||
template <typename VarT>
|
||||
class DefaultRandomSelector {
|
||||
private:
|
||||
mutable uint32_t m_seed;
|
||||
|
||||
public:
|
||||
constexpr explicit DefaultRandomSelector(uint32_t seed = 0x12345678) : m_seed(seed) {}
|
||||
|
||||
constexpr uint32_t rng(uint32_t max) const {
|
||||
m_seed = m_seed * 1103515245 + 12345;
|
||||
return m_seed % max;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Advanced random selector using std::mt19937 and std::uniform_int_distribution
|
||||
* This provides high-quality randomization for runtime use
|
||||
*/
|
||||
template <typename VarT>
|
||||
class AdvancedRandomSelector {
|
||||
private:
|
||||
std::mt19937& m_rng;
|
||||
|
||||
public:
|
||||
explicit AdvancedRandomSelector(std::mt19937& rng) : m_rng(rng) {}
|
||||
|
||||
uint32_t rng(uint32_t max) const {
|
||||
std::uniform_int_distribution<uint32_t> dist(0, max);
|
||||
return dist(m_rng);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
66
include/nd-wfc/wfc_utils.hpp
Normal file
66
include/nd-wfc/wfc_utils.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
namespace WFC
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
# ifdef _DEBUG
|
||||
inline constexpr void constexpr_assert(bool condition, const char* message = "")
|
||||
{
|
||||
if (!condition) throw message;
|
||||
}
|
||||
#else
|
||||
inline constexpr void constexpr_assert(bool condition, const char* message = "")
|
||||
{
|
||||
(void)condition;
|
||||
(void)message;
|
||||
}
|
||||
# endif
|
||||
|
||||
template <size_t Size>
|
||||
using MinimumIntegerType = std::conditional_t<Size <= std::numeric_limits<uint8_t>::max(), uint8_t,
|
||||
std::conditional_t<Size <= std::numeric_limits<uint16_t>::max(), uint16_t,
|
||||
std::conditional_t<Size <= std::numeric_limits<uint32_t>::max(), uint32_t,
|
||||
uint64_t>>>;
|
||||
|
||||
template <uint8_t bits>
|
||||
using MinimumBitsType = std::conditional_t<bits <= 8, uint8_t,
|
||||
std::conditional_t<bits <= 16, uint16_t,
|
||||
std::conditional_t<bits <= 32, uint32_t,
|
||||
std::conditional_t<bits <= 64, uint64_t,
|
||||
void>>>>;
|
||||
|
||||
|
||||
inline int FindNthSetBit(size_t num, int n) {
|
||||
constexpr_assert(n < std::popcount(num), "index is out of range");
|
||||
int bitCount = 0;
|
||||
while (num) {
|
||||
if (bitCount == n) {
|
||||
return std::countr_zero(num); // Index of the current set bit
|
||||
}
|
||||
bitCount++;
|
||||
num &= (num - 1); // turn of lowest set bit
|
||||
}
|
||||
return bitCount;
|
||||
}
|
||||
|
||||
template <typename VarT>
|
||||
struct WorldValue
|
||||
{
|
||||
public:
|
||||
WorldValue() = default;
|
||||
WorldValue(VarT value, uint16_t internalIndex)
|
||||
: Value(value)
|
||||
, InternalIndex(internalIndex)
|
||||
{}
|
||||
public:
|
||||
operator VarT() const { return Value; }
|
||||
|
||||
public:
|
||||
VarT Value{};
|
||||
uint16_t InternalIndex{};
|
||||
};
|
||||
|
||||
}
|
||||
113
include/nd-wfc/wfc_variable_map.hpp
Normal file
113
include/nd-wfc/wfc_variable_map.hpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include "wfc_utils.hpp"
|
||||
|
||||
namespace WFC {
|
||||
|
||||
/**
|
||||
* @brief Class to map variable values to indices at compile time
|
||||
*
|
||||
* This class is used to map variable values to indices at compile time.
|
||||
* It is a compile-time map of variable values to indices.
|
||||
*/
|
||||
|
||||
template <size_t VariablesAmount>
|
||||
using VariableIDType = std::conditional_t<VariablesAmount <= std::numeric_limits<uint8_t>::max(), uint8_t, uint16_t>;
|
||||
|
||||
|
||||
template <typename VarT, VarT ... Values>
|
||||
class VariableIDMap {
|
||||
public:
|
||||
|
||||
template <VarT ... AdditionalValues>
|
||||
using Merge = VariableIDMap<VarT, Values..., AdditionalValues...>;
|
||||
|
||||
using VariableIDT = VariableIDType<sizeof...(Values)>;
|
||||
|
||||
template <VarT Value>
|
||||
static consteval bool HasValue()
|
||||
{
|
||||
constexpr VarT arr[] = {Values...};
|
||||
|
||||
for (size_t i = 0; i < size(); ++i)
|
||||
if (arr[i] == Value)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <VarT Value>
|
||||
static consteval size_t GetIndex()
|
||||
{
|
||||
static_assert(HasValue<Value>(), "Value was not defined");
|
||||
constexpr VarT arr[] = {Values...};
|
||||
|
||||
for (size_t i = 0; i < size(); ++i)
|
||||
if (arr[i] == Value)
|
||||
return i;
|
||||
|
||||
return static_cast<size_t>(-1); // This line is unreachable if value is found
|
||||
}
|
||||
|
||||
static std::span<const VarT> GetAllValues()
|
||||
{
|
||||
static const VarT allValues[]
|
||||
{
|
||||
Values...
|
||||
};
|
||||
return std::span<const VarT>{ allValues, size() };
|
||||
}
|
||||
|
||||
static constexpr VarT GetValue(size_t index) {
|
||||
constexpr_assert(index < size());
|
||||
return GetAllValues()[index];
|
||||
}
|
||||
|
||||
static consteval size_t size() { return sizeof...(Values); }
|
||||
|
||||
template <VarT ... ValuesSlice>
|
||||
static constexpr auto ValuesToIndices() -> std::array<size_t, sizeof...(ValuesSlice)> {
|
||||
std::array<size_t, sizeof...(ValuesSlice)> indices = {GetIndex<ValuesSlice>()...};
|
||||
return indices;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename VarT, size_t Start, size_t End>
|
||||
class VariableIDRange
|
||||
{
|
||||
public:
|
||||
using Type = VarT;
|
||||
using VariableIDT = VariableIDType<End - Start>;
|
||||
|
||||
static_assert(Start < End, "Start must be less than End");
|
||||
static_assert(std::numeric_limits<VarT>::min() <= Start, "VarT must be able to represent all values in the range");
|
||||
static_assert(std::numeric_limits<VarT>::max() >= End, "VarT must be able to represent all values in the range");
|
||||
|
||||
static constexpr size_t size() { return End - Start; }
|
||||
|
||||
template <VarT Value>
|
||||
static consteval bool HasValue()
|
||||
{
|
||||
return Value >= Start && Value < End;
|
||||
}
|
||||
|
||||
template <VarT Value>
|
||||
static consteval size_t GetIndex()
|
||||
{
|
||||
return Value - Start;
|
||||
}
|
||||
|
||||
static constexpr VarT GetValue(size_t index)
|
||||
{
|
||||
return Start + index;
|
||||
}
|
||||
|
||||
template <VarT ... ValuesSlice>
|
||||
static constexpr auto ValuesToIndices() -> std::array<size_t, sizeof...(ValuesSlice)> {
|
||||
std::array<size_t, sizeof...(ValuesSlice)> indices = {GetIndex<ValuesSlice>()...};
|
||||
return indices;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
52
include/nd-wfc/wfc_wave.hpp
Normal file
52
include/nd-wfc/wfc_wave.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "wfc_bit_container.hpp"
|
||||
#include "wfc_variable_map.hpp"
|
||||
#include "wfc_allocator.hpp"
|
||||
|
||||
namespace WFC {
|
||||
|
||||
template <typename VariableIDMapT, typename WeightsMapT, size_t Size = 0>
|
||||
class Wave {
|
||||
public:
|
||||
using BitContainerT = BitContainer<VariableIDMapT::size(), Size>;
|
||||
using ElementT = typename BitContainerT::StorageType;
|
||||
using IDMapT = VariableIDMapT;
|
||||
using WeightContainersT = typename WeightsMapT::template WeightContainersT<Size, WFCStackAllocator>;
|
||||
using VariableIDT = typename VariableIDMapT::VariableIDT;
|
||||
using WeightT = typename WeightsMapT::WeightT;
|
||||
|
||||
static constexpr size_t ElementsAmount = Size;
|
||||
|
||||
public:
|
||||
Wave() = default;
|
||||
Wave(size_t size, size_t variableAmount, WFCStackAllocator& allocator) : m_data(size, allocator)
|
||||
{
|
||||
for (auto& wave : m_data) wave = (1 << variableAmount) - 1;
|
||||
}
|
||||
|
||||
Wave(const Wave& other) = default;
|
||||
|
||||
public:
|
||||
void Collapse(size_t index, ElementT mask) { m_data[index] &= mask; }
|
||||
size_t size() const { return m_data.size(); }
|
||||
size_t Entropy(size_t index) const { return std::popcount(m_data[index]); }
|
||||
bool IsCollapsed(size_t index) const { return Entropy(index) == 1; }
|
||||
bool IsFullyCollapsed() const { return std::all_of(m_data.begin(), m_data.end(), [](ElementT value) { return std::popcount(value) == 1; }); }
|
||||
bool HasContradiction() const { return std::any_of(m_data.begin(), m_data.end(), [](ElementT value) { return value == 0; }); }
|
||||
bool IsContradicted(size_t index) const { return m_data[index] == 0; }
|
||||
uint16_t GetVariableID(size_t index) const { return static_cast<uint16_t>(std::countr_zero(m_data[index])); }
|
||||
ElementT GetMask(size_t index) const { return m_data[index]; }
|
||||
|
||||
void SetWeight(VariableIDT containerIndex, size_t elementIndex, double weight) { m_weights.SetValueFloat(containerIndex, elementIndex, weight); }
|
||||
|
||||
template <size_t MaxWeight>
|
||||
WeightT GetWeight(VariableIDT containerIndex, size_t elementIndex) const { return m_weights.template GetValue<MaxWeight>(containerIndex, elementIndex); }
|
||||
|
||||
|
||||
private:
|
||||
BitContainerT m_data;
|
||||
WeightContainersT m_weights;
|
||||
};
|
||||
|
||||
}
|
||||
234
include/nd-wfc/wfc_weights.hpp
Normal file
234
include/nd-wfc/wfc_weights.hpp
Normal file
@@ -0,0 +1,234 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <tuple>
|
||||
|
||||
#include "wfc_bit_container.hpp"
|
||||
#include "wfc_utils.hpp"
|
||||
|
||||
namespace WFC {
|
||||
|
||||
template <typename VariableMap, uint8_t Precision>
|
||||
struct PrecisionEntry
|
||||
{
|
||||
|
||||
constexpr static uint8_t PrecisionValue = Precision;
|
||||
|
||||
template <typename MainVariableMap>
|
||||
constexpr static bool UpdatePrecisions(std::span<uint8_t> precisions)
|
||||
{
|
||||
constexpr auto SelectedEntries = VariableMap::GetAllValues();
|
||||
for (auto entry : SelectedEntries)
|
||||
{
|
||||
precisions[MainVariableMap::GetIndex(entry)] = Precision;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
enum class EPrecision : uint8_t
|
||||
{
|
||||
Precision_0 = 0,
|
||||
Precision_2 = 2,
|
||||
Precision_4 = 4,
|
||||
Precision_8 = 8,
|
||||
Precision_16 = 16,
|
||||
Precision_32 = 32,
|
||||
Precision_64 = 64,
|
||||
};
|
||||
|
||||
template <size_t Size, typename AllocatorT, EPrecision ... Precisions>
|
||||
class WeightContainers
|
||||
{
|
||||
private:
|
||||
template <EPrecision Precision>
|
||||
using BitContainerT = BitContainer<static_cast<uint8_t>(Precision), Size, AllocatorT>;
|
||||
|
||||
using TupleT = std::tuple<BitContainerT<Precisions>...>;
|
||||
TupleT m_WeightContainers;
|
||||
|
||||
static_assert(((static_cast<uint8_t>(Precisions) <= static_cast<uint8_t>(EPrecision::Precision_64)) && ...), "Cannot have precision larger than 64 (double precision)");
|
||||
|
||||
public:
|
||||
WeightContainers() = default;
|
||||
WeightContainers(size_t size)
|
||||
: m_WeightContainers{ BitContainerT<Precisions>(size, AllocatorT()) ... }
|
||||
{}
|
||||
WeightContainers(size_t size, AllocatorT& allocator)
|
||||
: m_WeightContainers{ BitContainerT<Precisions>(size, allocator) ... }
|
||||
{}
|
||||
|
||||
public:
|
||||
static constexpr size_t size()
|
||||
{
|
||||
return sizeof...(Precisions);
|
||||
}
|
||||
|
||||
/*
|
||||
template <typename ValueT>
|
||||
void SetValue(size_t containerIndex, size_t index, ValueT value)
|
||||
{
|
||||
SetValueFunctions<ValueT>()[containerIndex](*this, index, value);
|
||||
}
|
||||
*/
|
||||
void SetValueFloat(size_t containerIndex, size_t index, double value)
|
||||
{
|
||||
SetFloatValueFunctions()[containerIndex](*this, index, value);
|
||||
}
|
||||
|
||||
template <size_t MaxWeight>
|
||||
uint64_t GetValue(size_t containerIndex, size_t index)
|
||||
{
|
||||
return GetValueFunctions<MaxWeight>()[containerIndex](*this, index);
|
||||
}
|
||||
|
||||
private:
|
||||
/*
|
||||
template <typename ValueT>
|
||||
static constexpr auto& SetValueFunctions()
|
||||
{
|
||||
return SetValueFunctions<ValueT>(std::make_index_sequence<size()>());
|
||||
}
|
||||
|
||||
template <typename ValueT, size_t ... Is>
|
||||
static constexpr auto& SetValueFunctions(std::index_sequence<Is...>)
|
||||
{
|
||||
static constexpr std::array<void(*)(WeightContainers& weightContainers, size_t index, ValueT value), VariableIDMapT::size()> setValueFunctions =
|
||||
{
|
||||
[] (WeightContainers& weightContainers, size_t index, ValueT value) {
|
||||
std::get<Is>(weightContainers.m_WeightContainers)[index] = value;
|
||||
},
|
||||
...
|
||||
};
|
||||
return setValueFunctions;
|
||||
}
|
||||
*/
|
||||
static constexpr auto& SetFloatValueFunctions()
|
||||
{
|
||||
return SetFloatValueFunctions(std::make_index_sequence<size()>());
|
||||
}
|
||||
|
||||
template <size_t ... Is>
|
||||
static constexpr auto& SetFloatValueFunctions(std::index_sequence<Is...>)
|
||||
{
|
||||
using FunctionT = void(*)(WeightContainers& weightContainers, size_t index, double value);
|
||||
constexpr std::array<FunctionT, size()> setFloatValueFunctions
|
||||
{
|
||||
[](WeightContainers& weightContainers, size_t index, double value) -> FunctionT {
|
||||
|
||||
using BitContainerEntryT = typename WeightContainers::TupleT::template tuple_element<Is>::type;
|
||||
if constexpr (!std::is_same_v<BitContainerEntryT::StorageType, detail::Empty>)
|
||||
{
|
||||
constexpr_assert(value >= 0.0 && value <= 1.0, "Value must be between 0.0 and 1.0");
|
||||
std::get<Is>(weightContainers.m_WeightContainers)[index] = static_cast<BitContainerEntryT::StorageType>(value * BitContainerEntryT::MaxValue);
|
||||
}
|
||||
}
|
||||
...
|
||||
};
|
||||
return setFloatValueFunctions;
|
||||
}
|
||||
|
||||
template <size_t MaxWeight>
|
||||
static constexpr auto& GetValueFunctions()
|
||||
{
|
||||
return GetValueFunctions<MaxWeight>(std::make_index_sequence<size()>());
|
||||
}
|
||||
|
||||
template <size_t MaxWeight, size_t ... Is>
|
||||
static constexpr auto& GetValueFunctions(std::index_sequence<Is...>)
|
||||
{
|
||||
using FunctionT = uint64_t(*)(WeightContainers& weightContainers, size_t index);
|
||||
constexpr std::array<FunctionT, size()> getValueFunctions =
|
||||
{
|
||||
[] (WeightContainers& weightContainers, size_t index) -> FunctionT {
|
||||
using BitContainerEntryT = typename WeightContainers::TupleT::template tuple_element<Is>::type;
|
||||
|
||||
if constexpr (std::is_same_v<BitContainerEntryT::StorageType, detail::Empty>)
|
||||
{
|
||||
return MaxWeight / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
constexpr size_t maxValue = BitContainerEntryT::MaxValue;
|
||||
if constexpr (maxValue <= MaxWeight)
|
||||
{
|
||||
return std::get<Is>(weightContainers.m_WeightContainers)[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
return static_cast<uint64_t>(std::get<Is>(weightContainers.m_WeightContainers)[index]) * MaxWeight / maxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
};
|
||||
return getValueFunctions;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Compile-time weights storage for weighted random selection
|
||||
* @tparam VarT The variable type
|
||||
* @tparam VariableIDMapT The variable ID map type
|
||||
* @tparam DefaultWeight The default weight for values not explicitly specified
|
||||
* @tparam WeightSpecs Variadic template parameters of Weight<VarT, Value, Weight> specifications
|
||||
*/
|
||||
template <typename VariableIDMapT, typename ... PrecisionEntries>
|
||||
class WeightsMap {
|
||||
public:
|
||||
static constexpr std::array<uint8_t, VariableIDMapT::size()> GeneratePrecisionArray()
|
||||
{
|
||||
std::array<uint8_t, VariableIDMapT::size()> precisionArray{};
|
||||
|
||||
(PrecisionEntries::template UpdatePrecisions<VariableIDMapT>(precisionArray) && ...);
|
||||
|
||||
return precisionArray;
|
||||
}
|
||||
|
||||
static constexpr std::array<uint8_t, VariableIDMapT::size()> GetPrecisionArray()
|
||||
{
|
||||
constexpr std::array<uint8_t, VariableIDMapT::size()> precisionArray = GeneratePrecisionArray();
|
||||
return precisionArray;
|
||||
}
|
||||
|
||||
static constexpr size_t GetPrecision(size_t index)
|
||||
{
|
||||
return GetPrecisionArray()[index];
|
||||
}
|
||||
|
||||
static constexpr uint8_t GetMaxPrecision()
|
||||
{
|
||||
return std::max<uint8_t>({PrecisionEntries::PrecisionValue ...});
|
||||
}
|
||||
|
||||
static constexpr uint8_t GetMaxValue()
|
||||
{
|
||||
return (1 << GetMaxPrecision()) - 1;
|
||||
}
|
||||
|
||||
static constexpr bool HasWeights()
|
||||
{
|
||||
return sizeof...(PrecisionEntries) > 0;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
using VariablesT = VariableIDMapT;
|
||||
|
||||
template<size_t Size, typename AllocatorT, size_t... Is>
|
||||
auto MakeWeightContainersT(AllocatorT*, std::index_sequence<Is...>)
|
||||
-> WeightContainers<Size, AllocatorT, GetPrecision(Is) ...>;
|
||||
|
||||
template <size_t Size, typename AllocatorT>
|
||||
using WeightContainersT = decltype(
|
||||
MakeWeightContainersT<Size>(static_cast<AllocatorT*>(nullptr), std::make_index_sequence<VariableIDMapT::size()>{})
|
||||
);
|
||||
|
||||
template <typename PrecisionEntryT>
|
||||
using Merge = WeightsMap<VariableIDMapT, PrecisionEntries..., PrecisionEntryT>;
|
||||
|
||||
using WeightT = typename BitContainer<GetMaxPrecision(), 0, std::allocator<uint8_t>>::StorageType;
|
||||
};
|
||||
|
||||
}
|
||||
10
prompts/10-bit-container
Normal file
10
prompts/10-bit-container
Normal file
@@ -0,0 +1,10 @@
|
||||
We're using c++20, I want you to create a file wfc_bit_container.hpp in include\nd-wfc which contains a templated class whose goal is to minimize the amount of bits we use for masking. The template type should be a number (The number of bits stored) and should have the Size of the container (how many of these integers should we store). The size is 0 by default, which means it's resizable and it should use an std::vector instead of an std::array.
|
||||
examples of given parameters:
|
||||
- Bits: 2; Size: 8; -> 2 requires 2 bits, std::array<uint8_t, 2>
|
||||
- Bits: 3; size: 4; -> 3 requires 4 bits, std::array<uint8_t, 2>
|
||||
- Bits: 0; size 128; -> 0*128 == 0; std::array<uint8_t, 0>
|
||||
- Bits: 32; size: 100; -> std::array<uint32_t, 100>
|
||||
- Bits: 256; size: 0; -> std::vector<uint64_t[2]>
|
||||
Make sure that the amount of bits used is a power of 2: 0, 1, 2, 4, 8, 16, 32, 64. If more than 64 bits are required, make sure it is a multiple of 64: 64, 128, 196, 256, etc.
|
||||
We should be able to get/set values with an index, do bit operations like |, &, ^ on individual elements, do std::countl_zero, std::countl_one, std::countr_zero, std::countr_one & std::popcount.
|
||||
This repo tries to be as optimized as possible. Make good use of `asserts` instead of `if conditions` wherever you are checking something.
|
||||
@@ -9,6 +9,7 @@ FetchContent_MakeAvailable(googletest)
|
||||
|
||||
set(TEST_SOURCES
|
||||
test_main.cpp
|
||||
test_allocator.cpp
|
||||
)
|
||||
|
||||
# Create test executable
|
||||
|
||||
316
tests/test_allocator.cpp
Normal file
316
tests/test_allocator.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <cstring>
|
||||
#include "nd-wfc/wfc_allocator.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
// Test fixture for WFCStackAllocator tests
|
||||
class WFCStackAllocatorTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Setup if needed
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
// Cleanup if needed
|
||||
}
|
||||
};
|
||||
|
||||
// Test basic allocation and deallocation
|
||||
TEST_F(WFCStackAllocatorTest, BasicAllocation) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
void* ptr1 = allocator.allocate(64);
|
||||
ASSERT_NE(ptr1, nullptr);
|
||||
|
||||
void* ptr2 = allocator.allocate(128);
|
||||
ASSERT_NE(ptr2, nullptr);
|
||||
ASSERT_NE(ptr1, ptr2); // Should be different addresses
|
||||
|
||||
// Check that allocations are properly aligned
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr1) % 8, 0);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr2) % 8, 0);
|
||||
|
||||
// deallocate doesn't do anything in this allocator
|
||||
allocator.deallocate(ptr1);
|
||||
allocator.deallocate(ptr2);
|
||||
}
|
||||
|
||||
// Test alignment
|
||||
TEST_F(WFCStackAllocatorTest, Alignment) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
// Test various sizes and ensure 8-byte alignment
|
||||
for (size_t size : {1, 3, 7, 9, 15, 17}) {
|
||||
void* ptr = allocator.allocate(size);
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % 8, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Test stack frame functionality
|
||||
TEST_F(WFCStackAllocatorTest, StackFrame) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
// Allocate some memory in the root frame
|
||||
void* rootPtr = allocator.allocate(64);
|
||||
ASSERT_NE(rootPtr, nullptr);
|
||||
|
||||
size_t initialCapacity = allocator.getCapacity();
|
||||
|
||||
{
|
||||
// Create a new stack frame
|
||||
auto frame = allocator.createFrame();
|
||||
|
||||
// Allocate in the new frame
|
||||
void* framePtr1 = allocator.allocate(32);
|
||||
void* framePtr2 = allocator.allocate(48);
|
||||
ASSERT_NE(framePtr1, nullptr);
|
||||
ASSERT_NE(framePtr2, nullptr);
|
||||
|
||||
// Capacity should be reduced
|
||||
EXPECT_LT(allocator.getCapacity(), initialCapacity);
|
||||
|
||||
// Frame goes out of scope, memory should be freed
|
||||
}
|
||||
|
||||
// After frame destruction, capacity should be restored
|
||||
EXPECT_EQ(allocator.getCapacity(), initialCapacity);
|
||||
|
||||
// We can still allocate (should reuse the freed space)
|
||||
void* newPtr = allocator.allocate(32);
|
||||
ASSERT_NE(newPtr, nullptr);
|
||||
}
|
||||
|
||||
// Test nested stack frames
|
||||
TEST_F(WFCStackAllocatorTest, NestedStackFrames) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
void* rootPtr = allocator.allocate(32);
|
||||
size_t rootCapacity = allocator.getCapacity();
|
||||
|
||||
{
|
||||
auto frame1 = allocator.createFrame();
|
||||
void* frame1Ptr = allocator.allocate(32);
|
||||
size_t frame1Capacity = allocator.getCapacity();
|
||||
|
||||
{
|
||||
auto frame2 = allocator.createFrame();
|
||||
void* frame2Ptr = allocator.allocate(32);
|
||||
size_t frame2Capacity = allocator.getCapacity();
|
||||
|
||||
// Each nested frame should have less capacity
|
||||
EXPECT_LT(frame2Capacity, frame1Capacity);
|
||||
EXPECT_LT(frame1Capacity, rootCapacity);
|
||||
|
||||
// frame2 goes out of scope
|
||||
}
|
||||
|
||||
// Back to frame1 capacity
|
||||
EXPECT_EQ(allocator.getCapacity(), frame1Capacity);
|
||||
|
||||
// frame1 goes out of scope
|
||||
}
|
||||
|
||||
// Back to root capacity
|
||||
EXPECT_EQ(allocator.getCapacity(), rootCapacity);
|
||||
}
|
||||
|
||||
// Test automatic pool expansion
|
||||
TEST_F(WFCStackAllocatorTest, PoolExpansion) {
|
||||
WFC::WFCStackAllocator allocator(128); // Small initial pool
|
||||
|
||||
// Allocate until we exceed the initial pool
|
||||
std::vector<void*> allocations;
|
||||
size_t totalAllocated = 0;
|
||||
|
||||
while (totalAllocated < 1000) { // More than initial capacity
|
||||
void* ptr = allocator.allocate(64);
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
allocations.push_back(ptr);
|
||||
totalAllocated += 64;
|
||||
}
|
||||
|
||||
// All allocations should be valid and aligned
|
||||
for (void* ptr : allocations) {
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % 8, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Test constructor with user-provided memory
|
||||
TEST_F(WFCStackAllocatorTest, UserProvidedMemory) {
|
||||
const size_t bufferSize = 512;
|
||||
std::vector<uint8_t> buffer(bufferSize);
|
||||
|
||||
std::span<uint8_t> span(buffer.data(), buffer.size());
|
||||
WFC::WFCStackAllocator allocator(span);
|
||||
|
||||
// Should be able to allocate from the provided buffer
|
||||
void* ptr1 = allocator.allocate(64);
|
||||
ASSERT_NE(ptr1, nullptr);
|
||||
|
||||
// Pointer should be within our buffer
|
||||
EXPECT_GE(ptr1, buffer.data());
|
||||
EXPECT_LT(static_cast<uint8_t*>(ptr1) + 64, buffer.data() + bufferSize);
|
||||
|
||||
void* ptr2 = allocator.allocate(128);
|
||||
ASSERT_NE(ptr2, nullptr);
|
||||
|
||||
// Should still be able to expand to new pools when user buffer is exhausted
|
||||
void* ptr3 = allocator.allocate(bufferSize); // Larger than user buffer
|
||||
ASSERT_NE(ptr3, nullptr);
|
||||
}
|
||||
|
||||
// Test allocator adapter
|
||||
TEST_F(WFCStackAllocatorTest, AllocatorAdapter) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
WFC::WFCStackAllocatorAdapter<int> adapter(allocator);
|
||||
|
||||
// Test allocation
|
||||
int* ptr = adapter.allocate(10); // Space for 10 ints
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
|
||||
// Should be properly aligned for int
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % alignof(int), 0);
|
||||
|
||||
// Test deallocation
|
||||
adapter.deallocate(ptr, 10);
|
||||
}
|
||||
|
||||
// Test STL container with custom allocator
|
||||
TEST_F(WFCStackAllocatorTest, STLContainerWithAdapter) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
using IntVector = std::vector<int, WFC::WFCStackAllocatorAdapter<int>>;
|
||||
|
||||
{
|
||||
auto frame = allocator.createFrame();
|
||||
IntVector vec((WFC::WFCStackAllocatorAdapter<int>(allocator)));
|
||||
|
||||
// Add some elements
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
vec.push_back(i);
|
||||
}
|
||||
|
||||
EXPECT_EQ(vec.size(), 10);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
EXPECT_EQ(vec[i], i);
|
||||
}
|
||||
|
||||
// Vector goes out of scope, memory freed with frame
|
||||
}
|
||||
|
||||
// Should be able to allocate again (reusing freed memory)
|
||||
void* newAlloc = allocator.allocate(64);
|
||||
ASSERT_NE(newAlloc, nullptr);
|
||||
}
|
||||
|
||||
// Test alignment helper function
|
||||
TEST_F(WFCStackAllocatorTest, AlignUp) {
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(0), 0);
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(1), 8);
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(7), 8);
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(8), 8);
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(9), 16);
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(15), 16);
|
||||
EXPECT_EQ(WFC::WFCStackAllocator::alignUp(16), 16);
|
||||
}
|
||||
|
||||
// Test edge case: allocate zero bytes
|
||||
TEST_F(WFCStackAllocatorTest, ZeroAllocation) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
void* ptr = allocator.allocate(0);
|
||||
// Should return a valid pointer (can be null or non-null depending on implementation)
|
||||
// But should not crash
|
||||
|
||||
allocator.deallocate(ptr);
|
||||
}
|
||||
|
||||
// Test edge case: very large allocation
|
||||
TEST_F(WFCStackAllocatorTest, LargeAllocation) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
// Allocate something larger than initial capacity
|
||||
void* ptr = allocator.allocate(2000);
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % 8, 0);
|
||||
}
|
||||
|
||||
// Test memory reuse after frame destruction
|
||||
TEST_F(WFCStackAllocatorTest, MemoryReuse) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
size_t initialCapacity = allocator.getCapacity();
|
||||
|
||||
{
|
||||
auto frame = allocator.createFrame();
|
||||
allocator.allocate(64);
|
||||
allocator.allocate(64);
|
||||
|
||||
// Should have less capacity during frame
|
||||
EXPECT_LT(allocator.getCapacity(), initialCapacity);
|
||||
}
|
||||
|
||||
// After frame destruction, should be back to initial capacity
|
||||
EXPECT_EQ(allocator.getCapacity(), initialCapacity);
|
||||
}
|
||||
|
||||
// Test multiple frames and complex nesting
|
||||
TEST_F(WFCStackAllocatorTest, ComplexFrameNesting) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
// Root allocations
|
||||
void* root1 = allocator.allocate(32);
|
||||
void* root2 = allocator.allocate(32);
|
||||
|
||||
{
|
||||
auto frame1 = allocator.createFrame();
|
||||
void* f1_1 = allocator.allocate(32);
|
||||
void* f1_2 = allocator.allocate(32);
|
||||
|
||||
{
|
||||
auto frame2 = allocator.createFrame();
|
||||
void* f2_1 = allocator.allocate(32);
|
||||
|
||||
{
|
||||
auto frame3 = allocator.createFrame();
|
||||
void* f3_1 = allocator.allocate(32);
|
||||
// frame3 ends
|
||||
}
|
||||
|
||||
void* f2_2 = allocator.allocate(32);
|
||||
// frame2 ends
|
||||
}
|
||||
|
||||
void* f1_3 = allocator.allocate(32);
|
||||
// frame1 ends
|
||||
}
|
||||
|
||||
// All frame memory should be freed, root allocations still valid
|
||||
void* root3 = allocator.allocate(32); // Should reuse freed space
|
||||
ASSERT_NE(root3, nullptr);
|
||||
}
|
||||
|
||||
// Test that deallocation is a no-op
|
||||
TEST_F(WFCStackAllocatorTest, DeallocateIsNoOp) {
|
||||
WFC::WFCStackAllocator allocator(1024);
|
||||
|
||||
void* ptr = allocator.allocate(64);
|
||||
size_t capacityBefore = allocator.getCapacity();
|
||||
|
||||
// deallocate should do nothing
|
||||
allocator.deallocate(ptr);
|
||||
|
||||
// Capacity should be unchanged
|
||||
EXPECT_EQ(allocator.getCapacity(), capacityBefore);
|
||||
|
||||
// Should be able to allocate again
|
||||
void* ptr2 = allocator.allocate(64);
|
||||
ASSERT_NE(ptr2, nullptr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -2,8 +2,11 @@
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include "nd-wfc/wfc.hpp"
|
||||
#include "nd-wfc/worlds.hpp"
|
||||
#include "nd-wfc/wfc_allocator.hpp"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
Reference in New Issue
Block a user