Merge commit 'a51b88cccfbc2e51fbf5ce64b564002000548ec5' into Prompt/Allocator

This commit is contained in:
cdemeyer-teachx
2025-08-25 10:08:40 +09:00
2 changed files with 262 additions and 13 deletions

View File

@@ -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);

View 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