stack allocator integration
This commit is contained in:
@@ -11,7 +11,8 @@
|
||||
#include <algorithm>
|
||||
#include <concepts>
|
||||
#include <bit>
|
||||
#include <iostream>
|
||||
|
||||
#include "wfc_allocator.hpp"
|
||||
|
||||
namespace WFC {
|
||||
|
||||
@@ -144,7 +145,7 @@ template <typename MaskType>
|
||||
class Wave {
|
||||
public:
|
||||
Wave() = default;
|
||||
Wave(size_t size, size_t variableAmount) : m_data(size)
|
||||
Wave(size_t size, size_t variableAmount, WFCStackAllocator& allocator) : m_data(size, WFCStackAllocatorAdapter<MaskType>(allocator))
|
||||
{
|
||||
for (auto& wave : m_data) wave = (1 << variableAmount) - 1;
|
||||
}
|
||||
@@ -163,7 +164,7 @@ public:
|
||||
MaskType GetMask(size_t index) const { return m_data[index]; }
|
||||
|
||||
private:
|
||||
std::vector<MaskType> m_data;
|
||||
WFCVector<MaskType> m_data;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -175,7 +176,7 @@ public:
|
||||
using MaskType = typename VariableIDMapT::MaskType;
|
||||
|
||||
public:
|
||||
Constrainer(Wave<MaskType>& wave, std::queue<size_t>& propagationQueue)
|
||||
Constrainer(Wave<MaskType>& wave, WFCQueue<size_t>& propagationQueue)
|
||||
: m_wave(wave)
|
||||
, m_propagationQueue(propagationQueue)
|
||||
{}
|
||||
@@ -224,7 +225,7 @@ private:
|
||||
|
||||
private:
|
||||
Wave<MaskType>& m_wave;
|
||||
std::queue<size_t>& m_propagationQueue;
|
||||
WFCQueue<size_t>& m_propagationQueue;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -255,14 +256,17 @@ public:
|
||||
public:
|
||||
struct SolverState {
|
||||
WorldT& world;
|
||||
std::queue<size_t> propagationQueue{};
|
||||
Wave<MaskType> wave{};
|
||||
WFCQueue<size_t> propagationQueue;
|
||||
Wave<MaskType> wave;
|
||||
std::mt19937& rng;
|
||||
WFCStackAllocator& allocator;
|
||||
|
||||
SolverState(WorldT& world, size_t variableAmount, std::mt19937& rng)
|
||||
SolverState(WorldT& world, size_t variableAmount, std::mt19937& rng, WFCStackAllocator& allocator)
|
||||
: world(world)
|
||||
, wave(world.size(), variableAmount)
|
||||
, propagationQueue{ WFCStackAllocatorAdapter<size_t>(allocator) }
|
||||
, wave{ world.size(), variableAmount, allocator }
|
||||
, rng(rng)
|
||||
, allocator(allocator)
|
||||
{}
|
||||
|
||||
SolverState(const SolverState& other) = default;
|
||||
@@ -276,10 +280,9 @@ public:
|
||||
public:
|
||||
bool Run(WorldT& world, bool propagateInitialValues = false)
|
||||
{
|
||||
//auto seed = std::random_device{}();
|
||||
auto seed = 1844803044ul;
|
||||
std::mt19937 random{ seed };
|
||||
SolverState state(world, m_variables.size(), random);
|
||||
WFCStackAllocator allocator{};
|
||||
std::mt19937 random{ std::random_device{}() };
|
||||
SolverState state(world, m_variables.size(), random, allocator);
|
||||
return Run(state, propagateInitialValues);
|
||||
}
|
||||
|
||||
@@ -388,6 +391,7 @@ private:
|
||||
|
||||
{
|
||||
// copy the state and branch out
|
||||
auto stackFrame = state.allocator.createFrame();
|
||||
SolverState newState(state);
|
||||
newState.wave.Collapse(minEntropyCell, 1 << selectedValue);
|
||||
newState.propagationQueue.push(minEntropyCell);
|
||||
|
||||
245
include/nd-wfc/wfc_allocator.hpp
Normal file
245
include/nd-wfc/wfc_allocator.hpp
Normal file
@@ -0,0 +1,245 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <cstring>
|
||||
|
||||
namespace WFC {
|
||||
|
||||
/**
|
||||
* @brief Stack allocator specifically designed for WFC branching operations
|
||||
*
|
||||
* This allocator uses a stack-based approach where memory is allocated in chunks
|
||||
* and automatically deallocated when branches go out of scope. It's optimized
|
||||
* for the recursive branching pattern used in the WFC algorithm.
|
||||
*/
|
||||
class WFCStackAllocator {
|
||||
private:
|
||||
struct Block {
|
||||
void* ptr;
|
||||
size_t size;
|
||||
size_t alignment;
|
||||
|
||||
Block() : ptr(nullptr), size(0), alignment(0) {}
|
||||
Block(void* p, size_t s, size_t a) : ptr(p), size(s), alignment(a) {}
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct allocator with initial capacity
|
||||
* @param initialCapacity Initial memory pool size in bytes
|
||||
*/
|
||||
explicit WFCStackAllocator(size_t initialCapacity = 1024 * 1024) // 1MB default
|
||||
: m_capacity(initialCapacity)
|
||||
, m_used(0)
|
||||
, m_pool(nullptr)
|
||||
{
|
||||
m_pool = std::aligned_alloc(64, m_capacity); // 64-byte alignment for cache efficiency
|
||||
if (!m_pool) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
}
|
||||
|
||||
~WFCStackAllocator() {
|
||||
if (m_pool) {
|
||||
std::free(m_pool);
|
||||
}
|
||||
}
|
||||
|
||||
// Non-copyable, non-movable for safety
|
||||
WFCStackAllocator(const WFCStackAllocator&) = delete;
|
||||
WFCStackAllocator& operator=(const WFCStackAllocator&) = delete;
|
||||
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) {
|
||||
// Align the current position
|
||||
size_t alignedUsed = alignUp(m_used, alignment);
|
||||
|
||||
// Check if we have enough space
|
||||
if (alignedUsed + size > m_capacity) {
|
||||
// Grow the pool if needed
|
||||
growPool(alignedUsed + size);
|
||||
}
|
||||
|
||||
void* ptr = static_cast<char*>(m_pool) + alignedUsed;
|
||||
m_used = alignedUsed + size;
|
||||
|
||||
// Track this allocation for proper cleanup
|
||||
m_allocations.emplace_back(ptr, size, alignment);
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a stack frame marker for RAII-based cleanup
|
||||
* @return StackFrame object that will cleanup on destruction
|
||||
*/
|
||||
class StackFrame {
|
||||
private:
|
||||
WFCStackAllocator& m_allocator;
|
||||
size_t m_savedUsed;
|
||||
size_t m_savedAllocCount;
|
||||
|
||||
public:
|
||||
StackFrame(WFCStackAllocator& allocator)
|
||||
: m_allocator(allocator)
|
||||
, m_savedUsed(allocator.m_used)
|
||||
, m_savedAllocCount(allocator.m_allocations.size())
|
||||
{}
|
||||
|
||||
~StackFrame() {
|
||||
// Restore the stack to the previous state
|
||||
m_allocator.m_used = m_savedUsed;
|
||||
m_allocator.m_allocations.resize(m_savedAllocCount);
|
||||
}
|
||||
|
||||
// Non-copyable, movable
|
||||
StackFrame(const StackFrame&) = delete;
|
||||
StackFrame& operator=(const StackFrame&) = delete;
|
||||
StackFrame(StackFrame&&) = default;
|
||||
StackFrame& operator=(StackFrame&&) = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create a new stack frame for a branch
|
||||
*/
|
||||
StackFrame createFrame() {
|
||||
return StackFrame(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get current memory usage
|
||||
*/
|
||||
size_t getUsed() const { return m_used; }
|
||||
|
||||
/**
|
||||
* @brief Get total capacity
|
||||
*/
|
||||
size_t getCapacity() const { return m_capacity; }
|
||||
|
||||
/**
|
||||
* @brief Get allocation count
|
||||
*/
|
||||
size_t getAllocationCount() const { return m_allocations.size(); }
|
||||
|
||||
/**
|
||||
* @brief Reset the allocator (useful for reusing between WFC runs)
|
||||
*/
|
||||
void reset() {
|
||||
m_used = 0;
|
||||
m_allocations.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
size_t alignUp(size_t value, size_t alignment) const {
|
||||
return (value + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
void growPool(size_t requiredSize) {
|
||||
size_t newCapacity = std::max(m_capacity * 2, requiredSize);
|
||||
|
||||
void* newPool = std::aligned_alloc(64, newCapacity);
|
||||
if (!newPool) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
// Copy existing data
|
||||
std::memcpy(newPool, m_pool, m_used);
|
||||
|
||||
// Update all existing pointers in allocations
|
||||
ptrdiff_t offset = static_cast<char*>(newPool) - static_cast<char*>(m_pool);
|
||||
for (auto& block : m_allocations) {
|
||||
block.ptr = static_cast<char*>(block.ptr) + offset;
|
||||
}
|
||||
|
||||
std::free(m_pool);
|
||||
m_pool = newPool;
|
||||
m_capacity = newCapacity;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_capacity;
|
||||
size_t m_used;
|
||||
void* m_pool;
|
||||
std::vector<Block> m_allocations;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Custom allocator adapter for STL containers using WFCStackAllocator
|
||||
*/
|
||||
template<typename T>
|
||||
class WFCStackAllocatorAdapter {
|
||||
public:
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
template<typename U>
|
||||
struct rebind {
|
||||
using other = WFCStackAllocatorAdapter<U>;
|
||||
};
|
||||
|
||||
WFCStackAllocatorAdapter(WFCStackAllocator& allocator)
|
||||
: m_allocator(&allocator) {}
|
||||
|
||||
template<typename U>
|
||||
WFCStackAllocatorAdapter(const WFCStackAllocatorAdapter<U>& other)
|
||||
: m_allocator(other.m_allocator) {}
|
||||
|
||||
pointer allocate(size_type n) {
|
||||
return static_cast<pointer>(m_allocator->allocate(n * sizeof(T), alignof(T)));
|
||||
}
|
||||
|
||||
void deallocate(pointer ptr, size_type) {
|
||||
m_allocator->deallocate(ptr);
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
bool operator==(const WFCStackAllocatorAdapter<U>& other) const {
|
||||
return m_allocator == other.m_allocator;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
bool operator!=(const WFCStackAllocatorAdapter<U>& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user