non allocating queue
This commit is contained in:
@@ -282,13 +282,11 @@ void testPuzzleSolving(const std::string& difficulty, const std::string& filenam
|
|||||||
int solvedCount = 0;
|
int solvedCount = 0;
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
WFC::WFCStackAllocator allocator{};
|
|
||||||
|
|
||||||
for (size_t i = 0; i < puzzles.size(); ++i) {
|
for (size_t i = 0; i < puzzles.size(); ++i) {
|
||||||
Sudoku& sudoku = puzzles[i];
|
Sudoku& sudoku = puzzles[i];
|
||||||
EXPECT_TRUE(sudoku.isValid()) << difficulty << " puzzle " << i << " is not valid";
|
EXPECT_TRUE(sudoku.isValid()) << difficulty << " puzzle " << i << " is not valid";
|
||||||
|
|
||||||
SudokuSolver::Run(sudoku, allocator);
|
SudokuSolver::Run(sudoku);
|
||||||
|
|
||||||
EXPECT_TRUE(sudoku.isSolved()) << difficulty << " puzzle " << i << " was not solved. Puzzle string: " << sudoku.toString();
|
EXPECT_TRUE(sudoku.isSolved()) << difficulty << " puzzle " << i << " was not solved. Puzzle string: " << sudoku.toString();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <queue>
|
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -22,6 +21,7 @@
|
|||||||
#include "wfc_constrainer.hpp"
|
#include "wfc_constrainer.hpp"
|
||||||
#include "wfc_callbacks.hpp"
|
#include "wfc_callbacks.hpp"
|
||||||
#include "wfc_random.hpp"
|
#include "wfc_random.hpp"
|
||||||
|
#include "wfc_queue.hpp"
|
||||||
|
|
||||||
namespace WFC {
|
namespace WFC {
|
||||||
|
|
||||||
@@ -37,8 +37,8 @@ concept WorldType = requires(T world, size_t id, typename T::ValueType value) {
|
|||||||
* @brief Concept to validate constrainer function signature
|
* @brief Concept to validate constrainer function signature
|
||||||
* The function must be callable with parameters: (WorldT&, size_t, WorldValue<VarT>, Constrainer<VariableIDMapT>&)
|
* The function must be callable with parameters: (WorldT&, size_t, WorldValue<VarT>, Constrainer<VariableIDMapT>&)
|
||||||
*/
|
*/
|
||||||
template <typename T, typename WorldT, typename VarT, typename VariableIDMapT>
|
template <typename T, typename WorldT, typename VarT, typename VariableIDMapT, typename PropagationQueueType>
|
||||||
concept ConstrainerFunction = requires(T func, WorldT& world, size_t index, WorldValue<VarT> value, Constrainer<VariableIDMapT>& constrainer) {
|
concept ConstrainerFunction = requires(T func, WorldT& world, size_t index, WorldValue<VarT> value, Constrainer<VariableIDMapT, PropagationQueueType>& constrainer) {
|
||||||
func(world, index, value, constrainer);
|
func(world, index, value, constrainer);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,28 +73,23 @@ public:
|
|||||||
constexpr static size_t WorldSize = HasConstexprSize<WorldT> ? WorldT{}.size() : 0;
|
constexpr static size_t WorldSize = HasConstexprSize<WorldT> ? WorldT{}.size() : 0;
|
||||||
|
|
||||||
using WaveType = Wave<VariableIDMapT, WorldSize>;
|
using WaveType = Wave<VariableIDMapT, WorldSize>;
|
||||||
using ConstrainerType = Constrainer<WaveType>;
|
using PropagationQueueType = WFCQueue<WorldSize>;
|
||||||
|
using ConstrainerType = Constrainer<WaveType, PropagationQueueType>;
|
||||||
using MaskType = typename WaveType::ElementT;
|
using MaskType = typename WaveType::ElementT;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct SolverState
|
struct SolverState
|
||||||
{
|
{
|
||||||
WorldT& world;
|
WorldT& m_world;
|
||||||
WFCQueue<size_t> propagationQueue;
|
PropagationQueueType m_propagationQueue{};
|
||||||
WaveType wave;
|
RandomSelectorT m_randomSelector{};
|
||||||
std::mt19937& rng;
|
WFCStackAllocator m_allocator{};
|
||||||
RandomSelectorT& randomSelector;
|
size_t m_iterations{};
|
||||||
WFCStackAllocator& allocator;
|
|
||||||
size_t& iterations;
|
|
||||||
|
|
||||||
SolverState(WorldT& world, size_t variableAmount, std::mt19937& rng, RandomSelectorT& randomSelector, WFCStackAllocator& allocator, size_t& iterations)
|
SolverState(WorldT& world, uint32_t seed)
|
||||||
: world(world)
|
: m_world(world)
|
||||||
, propagationQueue{ WFCStackAllocatorAdapter<size_t>(allocator) }
|
, m_propagationQueue{ WorldSize ? WorldSize : static_cast<size_t>(world.size()) }
|
||||||
, wave{ WorldSize, variableAmount, allocator }
|
, m_randomSelector(seed)
|
||||||
, rng(rng)
|
|
||||||
, randomSelector(randomSelector)
|
|
||||||
, allocator(allocator)
|
|
||||||
, iterations(iterations)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
SolverState(const SolverState& other) = default;
|
SolverState(const SolverState& other) = default;
|
||||||
@@ -104,35 +99,12 @@ public:
|
|||||||
WFC() = delete; // dont make an instance of this class, only use the static methods.
|
WFC() = delete; // dont make an instance of this class, only use the static methods.
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static bool Run(WorldT& world, uint32_t seed = std::random_device{}())
|
static bool Run(WorldT& world, uint32_t seed = std::random_device{}())
|
||||||
{
|
{
|
||||||
WFCStackAllocator allocator{};
|
SolverState state{ world, seed };
|
||||||
return Run(world, allocator, seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool Run(WorldT& world, WFCStackAllocator& allocator, uint32_t seed = std::random_device{}())
|
|
||||||
{
|
|
||||||
allocator.reset();
|
|
||||||
constexpr_assert(allocator.getUsed() == 0, "Allocator must be empty");
|
|
||||||
|
|
||||||
size_t iterations = 0;
|
|
||||||
auto random = std::mt19937{ seed };
|
|
||||||
RandomSelectorT randomSelector{ seed };
|
|
||||||
SolverState state
|
|
||||||
{
|
|
||||||
world,
|
|
||||||
ConstrainerFunctionMapT::size(),
|
|
||||||
random,
|
|
||||||
randomSelector,
|
|
||||||
allocator,
|
|
||||||
iterations
|
|
||||||
};
|
|
||||||
bool result = Run(state);
|
bool result = Run(state);
|
||||||
|
|
||||||
allocator.reset();
|
|
||||||
constexpr_assert(allocator.getUsed() == 0, "Allocator must be empty");
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,43 +114,46 @@ public:
|
|||||||
*/
|
*/
|
||||||
static bool Run(SolverState& state)
|
static bool Run(SolverState& state)
|
||||||
{
|
{
|
||||||
PropogateInitialValues(state);
|
WaveType wave{ WorldSize, VariableIDMapT::ValuesRegisteredAmount, state.m_allocator };
|
||||||
|
|
||||||
if (RunLoop(state)) {
|
PropogateInitialValues(state, wave);
|
||||||
|
|
||||||
PopulateWorld(state);
|
if (RunLoop(state, wave)) {
|
||||||
|
|
||||||
|
PopulateWorld(state, wave);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool RunLoop(SolverState& state)
|
static bool RunLoop(SolverState& state, WaveType& wave)
|
||||||
{
|
{
|
||||||
for (; state.iterations < 1024 * 8; ++state.iterations)
|
static constexpr size_t MaxIterations = 1024 * 8;
|
||||||
|
for (; state.m_iterations < MaxIterations; ++state.m_iterations)
|
||||||
{
|
{
|
||||||
if (!Propagate(state))
|
if (!Propagate(state, wave))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (state.wave.HasContradiction())
|
if (wave.HasContradiction())
|
||||||
{
|
{
|
||||||
if constexpr (CallbacksT::HasContradictionCallback())
|
if constexpr (CallbacksT::HasContradictionCallback())
|
||||||
{
|
{
|
||||||
PopulateWorld(state);
|
PopulateWorld(state, wave);
|
||||||
typename CallbacksT::ContradictionCallback{}(state.world);
|
typename CallbacksT::ContradictionCallback{}(state.m_world);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.wave.IsFullyCollapsed())
|
if (wave.IsFullyCollapsed())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if constexpr (CallbacksT::HasBranchCallback())
|
if constexpr (CallbacksT::HasBranchCallback())
|
||||||
{
|
{
|
||||||
PopulateWorld(state);
|
PopulateWorld(state, wave);
|
||||||
typename CallbacksT::BranchCallback{}(state.world);
|
typename CallbacksT::BranchCallback{}(state.m_world);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Branch(state))
|
if (Branch(state, wave))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -189,9 +164,9 @@ public:
|
|||||||
* @param cellId The cell ID
|
* @param cellId The cell ID
|
||||||
* @return The value if collapsed, std::nullopt otherwise
|
* @return The value if collapsed, std::nullopt otherwise
|
||||||
*/
|
*/
|
||||||
static std::optional<VarT> GetValue(SolverState& state, int cellId) {
|
static std::optional<VarT> GetValue(WaveType& wave, int cellId) {
|
||||||
if (state.wave.IsCollapsed(cellId)) {
|
if (wave.IsCollapsed(cellId)) {
|
||||||
auto variableId = state.wave.GetVariableID(cellId);
|
auto variableId = wave.GetVariableID(cellId);
|
||||||
return VariableIDMapT::GetValue(variableId);
|
return VariableIDMapT::GetValue(variableId);
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -202,10 +177,10 @@ public:
|
|||||||
* @param cellId The cell ID
|
* @param cellId The cell ID
|
||||||
* @return Set of possible values
|
* @return Set of possible values
|
||||||
*/
|
*/
|
||||||
static const std::vector<VarT> GetPossibleValues(SolverState& state, int cellId)
|
static const std::vector<VarT> GetPossibleValues(WaveType& wave, int cellId)
|
||||||
{
|
{
|
||||||
std::vector<VarT> possibleValues;
|
std::vector<VarT> possibleValues;
|
||||||
MaskType mask = state.wave.GetMask(cellId);
|
MaskType mask = wave.GetMask(cellId);
|
||||||
for (size_t i = 0; i < ConstrainerFunctionMapT::size(); ++i) {
|
for (size_t i = 0; i < ConstrainerFunctionMapT::size(); ++i) {
|
||||||
if (mask & (1 << i)) possibleValues.push_back(VariableIDMapT::GetValue(i));
|
if (mask & (1 << i)) possibleValues.push_back(VariableIDMapT::GetValue(i));
|
||||||
}
|
}
|
||||||
@@ -213,29 +188,29 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void CollapseCell(SolverState& state, size_t cellId, uint16_t value)
|
static void CollapseCell(SolverState& state, WaveType& wave, size_t cellId, uint16_t value)
|
||||||
{
|
{
|
||||||
constexpr_assert(!state.wave.IsCollapsed(cellId) || state.wave.GetMask(cellId) == (MaskType(1) << value));
|
constexpr_assert(!wave.IsCollapsed(cellId) || wave.GetMask(cellId) == (MaskType(1) << value));
|
||||||
state.wave.Collapse(cellId, 1 << value);
|
wave.Collapse(cellId, 1 << value);
|
||||||
constexpr_assert(state.wave.IsCollapsed(cellId));
|
constexpr_assert(wave.IsCollapsed(cellId));
|
||||||
|
|
||||||
if constexpr (CallbacksT::HasCellCollapsedCallback())
|
if constexpr (CallbacksT::HasCellCollapsedCallback())
|
||||||
{
|
{
|
||||||
PopulateWorld(state);
|
PopulateWorld(state, wave);
|
||||||
typename CallbacksT::CellCollapsedCallback{}(state.world);
|
typename CallbacksT::CellCollapsedCallback{}(state.m_world);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool Branch(SolverState& state)
|
static bool Branch(SolverState& state, WaveType& wave)
|
||||||
{
|
{
|
||||||
constexpr_assert(state.propagationQueue.empty());
|
constexpr_assert(state.m_propagationQueue.empty());
|
||||||
|
|
||||||
// Find cell with minimum entropy > 1
|
// Find cell with minimum entropy > 1
|
||||||
size_t minEntropyCell = static_cast<size_t>(-1);
|
size_t minEntropyCell = static_cast<size_t>(-1);
|
||||||
size_t minEntropy = static_cast<size_t>(-1);
|
size_t minEntropy = static_cast<size_t>(-1);
|
||||||
|
|
||||||
for (size_t i = 0; i < state.wave.size(); ++i) {
|
for (size_t i = 0; i < wave.size(); ++i) {
|
||||||
size_t entropy = state.wave.Entropy(i);
|
size_t entropy = wave.Entropy(i);
|
||||||
if (entropy > 1 && entropy < minEntropy) {
|
if (entropy > 1 && entropy < minEntropy) {
|
||||||
minEntropy = entropy;
|
minEntropy = entropy;
|
||||||
minEntropyCell = i;
|
minEntropyCell = i;
|
||||||
@@ -243,12 +218,12 @@ private:
|
|||||||
}
|
}
|
||||||
if (minEntropyCell == static_cast<size_t>(-1)) return false;
|
if (minEntropyCell == static_cast<size_t>(-1)) return false;
|
||||||
|
|
||||||
constexpr_assert(!state.wave.IsCollapsed(minEntropyCell));
|
constexpr_assert(!wave.IsCollapsed(minEntropyCell));
|
||||||
|
|
||||||
// create a list of possible values
|
// create a list of possible values
|
||||||
uint16_t availableValues = static_cast<uint16_t>(state.wave.Entropy(minEntropyCell));
|
uint16_t availableValues = static_cast<uint16_t>(wave.Entropy(minEntropyCell));
|
||||||
std::array<uint16_t, VariableIDMapT::ValuesRegisteredAmount> possibleValues; // inplace vector
|
std::array<uint16_t, VariableIDMapT::ValuesRegisteredAmount> possibleValues; // inplace vector
|
||||||
MaskType mask = state.wave.GetMask(minEntropyCell);
|
MaskType mask = wave.GetMask(minEntropyCell);
|
||||||
for (size_t i = 0; i < availableValues; ++i)
|
for (size_t i = 0; i < availableValues; ++i)
|
||||||
{
|
{
|
||||||
uint16_t index = static_cast<uint16_t>(std::countr_zero(mask)); // get the index of the lowest set bit
|
uint16_t index = static_cast<uint16_t>(std::countr_zero(mask)); // get the index of the lowest set bit
|
||||||
@@ -269,29 +244,31 @@ private:
|
|||||||
valueArray[i] = VariableIDMapT::GetValue(possibleValues[i]);
|
valueArray[i] = VariableIDMapT::GetValue(possibleValues[i]);
|
||||||
}
|
}
|
||||||
std::span<const VarT> currentPossibleValues(valueArray.data(), availableValues);
|
std::span<const VarT> currentPossibleValues(valueArray.data(), availableValues);
|
||||||
size_t randomIndex = state.randomSelector(currentPossibleValues);
|
size_t randomIndex = state.m_randomSelector(currentPossibleValues);
|
||||||
size_t selectedValue = possibleValues[randomIndex];
|
size_t selectedValue = possibleValues[randomIndex];
|
||||||
|
|
||||||
{
|
{
|
||||||
// copy the state and branch out
|
// copy the state and branch out
|
||||||
auto stackFrame = state.allocator.createFrame();
|
auto stackFrame = state.m_allocator.createFrame();
|
||||||
SolverState newState(state);
|
auto queueFrame = state.m_propagationQueue.createBranchPoint();
|
||||||
CollapseCell(newState, minEntropyCell, static_cast<uint16_t>(selectedValue));
|
|
||||||
newState.propagationQueue.push(minEntropyCell);
|
auto newWave = wave;
|
||||||
|
CollapseCell(state, newWave, minEntropyCell, static_cast<uint16_t>(selectedValue));
|
||||||
|
state.m_propagationQueue.push(minEntropyCell);
|
||||||
|
|
||||||
if (RunLoop(newState))
|
if (RunLoop(state, newWave))
|
||||||
{
|
{
|
||||||
// copy the solution to the original state
|
// copy the solution to the original state
|
||||||
state.wave = newState.wave;
|
wave = newWave;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the failure state from the wave
|
// remove the failure state from the wave
|
||||||
constexpr_assert((state.wave.GetMask(minEntropyCell) & (MaskType(1) << selectedValue)) != 0, "Possible value was not set");
|
constexpr_assert((wave.GetMask(minEntropyCell) & (MaskType(1) << selectedValue)) != 0, "Possible value was not set");
|
||||||
state.wave.Collapse(minEntropyCell, ~(1 << selectedValue));
|
wave.Collapse(minEntropyCell, ~(1 << selectedValue));
|
||||||
constexpr_assert((state.wave.GetMask(minEntropyCell) & (MaskType(1) << selectedValue)) == 0, "Wave was not collapsed correctly");
|
constexpr_assert((wave.GetMask(minEntropyCell) & (MaskType(1) << selectedValue)) == 0, "Wave was not collapsed correctly");
|
||||||
|
|
||||||
// swap replacement value with the last value
|
// swap replacement value with the last value
|
||||||
std::swap(possibleValues[randomIndex], possibleValues[--availableValues]);
|
std::swap(possibleValues[randomIndex], possibleValues[--availableValues]);
|
||||||
@@ -300,47 +277,46 @@ private:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool Propagate(SolverState& state)
|
static bool Propagate(SolverState& state, WaveType& wave)
|
||||||
{
|
{
|
||||||
while (!state.propagationQueue.empty())
|
while (!state.m_propagationQueue.empty())
|
||||||
{
|
{
|
||||||
size_t cellId = state.propagationQueue.front();
|
size_t cellId = state.m_propagationQueue.pop();
|
||||||
state.propagationQueue.pop();
|
|
||||||
|
|
||||||
if (state.wave.IsContradicted(cellId)) return false;
|
if (wave.IsContradicted(cellId)) return false;
|
||||||
|
|
||||||
constexpr_assert(state.wave.IsCollapsed(cellId), "Cell was not collapsed");
|
constexpr_assert(wave.IsCollapsed(cellId), "Cell was not collapsed");
|
||||||
|
|
||||||
uint16_t variableID = state.wave.GetVariableID(cellId);
|
uint16_t variableID = wave.GetVariableID(cellId);
|
||||||
ConstrainerType constrainer(state.wave, state.propagationQueue);
|
ConstrainerType constrainer(wave, state.m_propagationQueue);
|
||||||
|
|
||||||
using ConstrainerFunctionPtrT = void(*)(WorldT&, size_t, WorldValue<VarT>, ConstrainerType&);
|
using ConstrainerFunctionPtrT = void(*)(WorldT&, size_t, WorldValue<VarT>, ConstrainerType&);
|
||||||
|
|
||||||
ConstrainerFunctionMapT::template GetFunction<ConstrainerFunctionPtrT>(variableID)(state.world, cellId, WorldValue<VarT>{VariableIDMapT::GetValue(variableID), variableID}, constrainer);
|
ConstrainerFunctionMapT::template GetFunction<ConstrainerFunctionPtrT>(variableID)(state.m_world, cellId, WorldValue<VarT>{VariableIDMapT::GetValue(variableID), variableID}, constrainer);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PopulateWorld(SolverState& state)
|
static void PopulateWorld(SolverState& state, WaveType& wave)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < state.wave.size(); ++i)
|
for (size_t i = 0; i < wave.size(); ++i)
|
||||||
{
|
{
|
||||||
if (state.wave.IsCollapsed(i))
|
if (wave.IsCollapsed(i))
|
||||||
state.world.setValue(i, VariableIDMapT::GetValue(state.wave.GetVariableID(i)));
|
state.m_world.setValue(i, VariableIDMapT::GetValue(wave.GetVariableID(i)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PropogateInitialValues(SolverState& state)
|
static void PropogateInitialValues(SolverState& state, WaveType& wave)
|
||||||
{
|
{
|
||||||
auto allValues = VariableIDMapT::GetAllValues();
|
auto allValues = VariableIDMapT::GetAllValues();
|
||||||
for (size_t i = 0; i < state.wave.size(); ++i)
|
for (size_t i = 0; i < wave.size(); ++i)
|
||||||
{
|
{
|
||||||
for (size_t j = 0; j < allValues.size(); ++j)
|
for (size_t j = 0; j < allValues.size(); ++j)
|
||||||
{
|
{
|
||||||
if (state.world.getValue(i) == allValues[j])
|
if (state.m_world.getValue(i) == allValues[j])
|
||||||
{
|
{
|
||||||
CollapseCell(state, static_cast<uint16_t>(i), static_cast<uint16_t>(j));
|
CollapseCell(state, wave, static_cast<uint16_t>(i), static_cast<uint16_t>(j));
|
||||||
state.propagationQueue.push(i);
|
state.m_propagationQueue.push(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -383,16 +383,4 @@ public:
|
|||||||
WFCStackAllocator* m_allocator;
|
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
|
} // namespace WFC
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ public:
|
|||||||
constexpr static size_t WorldSize = HasConstexprSize<WorldT> ? WorldT{}.size() : 0;
|
constexpr static size_t WorldSize = HasConstexprSize<WorldT> ? WorldT{}.size() : 0;
|
||||||
|
|
||||||
using WaveType = Wave<VariableIDMapT, WorldSize>;
|
using WaveType = Wave<VariableIDMapT, WorldSize>;
|
||||||
using ConstrainerType = Constrainer<WaveType>;
|
using PropagationQueueType = WFCQueue<WorldSize>;
|
||||||
|
using ConstrainerType = Constrainer<WaveType, PropagationQueueType>;
|
||||||
|
|
||||||
template <VarT ... Values>
|
template <VarT ... Values>
|
||||||
using DefineIDs = Builder<WorldT, VarT, typename VariableIDMapT::template Merge<Values...>, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT>;
|
using DefineIDs = Builder<WorldT, VarT, typename VariableIDMapT::template Merge<Values...>, ConstrainerFunctionMapT, CallbacksT, RandomSelectorT>;
|
||||||
|
|
||||||
template <typename ConstrainerFunctionT, VarT ... CorrespondingValues>
|
template <typename ConstrainerFunctionT, VarT ... CorrespondingValues>
|
||||||
requires ConstrainerFunction<ConstrainerFunctionT, WorldT, VarT, WaveType>
|
requires ConstrainerFunction<ConstrainerFunctionT, WorldT, VarT, WaveType, PropagationQueueType>
|
||||||
using DefineConstrainer = Builder<WorldT, VarT, VariableIDMapT,
|
using DefineConstrainer = Builder<WorldT, VarT, VariableIDMapT,
|
||||||
MergedConstrainerFunctionMap<
|
MergedConstrainerFunctionMap<
|
||||||
VariableIDMapT,
|
VariableIDMapT,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "wfc_variable_map.hpp"
|
#include "wfc_variable_map.hpp"
|
||||||
|
#include "wfc_queue.hpp"
|
||||||
|
|
||||||
namespace WFC {
|
namespace WFC {
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ using MergedConstrainerFunctionMap = decltype(
|
|||||||
/**
|
/**
|
||||||
* @brief Constrainer class used in constraint functions to limit possible values for other cells
|
* @brief Constrainer class used in constraint functions to limit possible values for other cells
|
||||||
*/
|
*/
|
||||||
template <typename WaveT>
|
template <typename WaveT, typename PropagationQueueT>
|
||||||
class Constrainer {
|
class Constrainer {
|
||||||
public:
|
public:
|
||||||
using IDMapT = typename WaveT::IDMapT;
|
using IDMapT = typename WaveT::IDMapT;
|
||||||
@@ -69,7 +70,7 @@ public:
|
|||||||
using MaskType = typename BitContainerT::StorageType;
|
using MaskType = typename BitContainerT::StorageType;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Constrainer(WaveT& wave, WFCQueue<size_t>& propagationQueue)
|
Constrainer(WaveT& wave, PropagationQueueT& propagationQueue)
|
||||||
: m_wave(wave)
|
: m_wave(wave)
|
||||||
, m_propagationQueue(propagationQueue)
|
, m_propagationQueue(propagationQueue)
|
||||||
{}
|
{}
|
||||||
@@ -120,7 +121,7 @@ private:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
WaveT& m_wave;
|
WaveT& m_wave;
|
||||||
WFCQueue<size_t>& m_propagationQueue;
|
PropagationQueueT& m_propagationQueue;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
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 "nd-wfc/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
|
||||||
Reference in New Issue
Block a user