Skip to content

Commit

Permalink
Back from the dead! Fixed some big performance issues; added a macro …
Browse files Browse the repository at this point in the history
…for debugging heap corruption.
  • Loading branch information
heyx3 committed Dec 10, 2024
1 parent 7d82170 commit f6bc489
Show file tree
Hide file tree
Showing 32 changed files with 472 additions and 205 deletions.
20 changes: 11 additions & 9 deletions WFC++.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_WINDLL;WFC_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_DEBUG;_WINDLL;WFC_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ObjectFileName>$(IntDir)\%(RelativeDir)\%(Filename).obj</ObjectFileName>
<LanguageStandard>stdcpp14</LanguageStandard>
<LanguageStandard>stdcpp20</LanguageStandard>
<DisableSpecificWarnings>4251;4984</DisableSpecificWarnings>
</ClCompile>
<PostBuildEvent>
Expand Down Expand Up @@ -131,9 +131,9 @@ del /q "%UNREAL_WFC_DIR%\Tiled"</Command>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_WINDLL;WFC_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_DEBUG;_WINDLL;WFC_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ObjectFileName>$(IntDir)\%(RelativeDir)\%(Filename).obj</ObjectFileName>
<LanguageStandard>stdcpp14</LanguageStandard>
<LanguageStandard>stdcpp20</LanguageStandard>
<DisableSpecificWarnings>4251;4984</DisableSpecificWarnings>
</ClCompile>
<PostBuildEvent>
Expand Down Expand Up @@ -171,9 +171,9 @@ del /q "%UNREAL_WFC_DIR%\Tiled"</Command>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_WINDLL;WFC_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>NDEBUG;_WINDLL;WFC_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ObjectFileName>$(IntDir)\%(RelativeDir)\%(Filename).obj</ObjectFileName>
<LanguageStandard>stdcpp14</LanguageStandard>
<LanguageStandard>stdcpp20</LanguageStandard>
<DisableSpecificWarnings>4251;4984</DisableSpecificWarnings>
</ClCompile>
<Link>
Expand Down Expand Up @@ -215,10 +215,11 @@ del /q "%UNREAL_WFC_DIR%\Tiled"</Command>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_WINDLL;WFC_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>NDEBUG;_WINDLL;WFC_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ObjectFileName>$(IntDir)\%(RelativeDir)\%(Filename).obj</ObjectFileName>
<LanguageStandard>stdcpp14</LanguageStandard>
<LanguageStandard>stdcpp20</LanguageStandard>
<DisableSpecificWarnings>4251;4984</DisableSpecificWarnings>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
Expand Down Expand Up @@ -260,6 +261,7 @@ del /q "%UNREAL_WFC_DIR%\Tiled"</Command>
<ClInclude Include="WFC++\EnumFlags.h" />
<ClInclude Include="WFC++\HelperClasses.h" />
<ClInclude Include="WFC++\List.hpp" />
<ClInclude Include="WFC++\MemoryChecks.h" />
<ClInclude Include="WFC++\Tiled3D\StandardRunner.h" />
<ClInclude Include="WFC++\WFCppStreamPrinting.hpp" />
<ClInclude Include="WFC++\Tiled3D\Grid.h" />
Expand All @@ -283,6 +285,7 @@ del /q "%UNREAL_WFC_DIR%\Tiled"</Command>
<ClInclude Include="WFC++\xoshiro.hpp" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="WFC++\HelperSrc\Vector2i.cpp" />
<ClCompile Include="WFC++\Simple\InputData.cpp" />
<ClCompile Include="WFC++\Simple\Pattern.cpp" />
<ClCompile Include="WFC++\Simple\State.cpp" />
Expand All @@ -293,7 +296,6 @@ del /q "%UNREAL_WFC_DIR%\Tiled"</Command>
<ClCompile Include="WFC++\Tiled\InputData.cpp" />
<ClCompile Include="WFC++\Tiled\State.cpp" />
<ClCompile Include="WFC++\Tiled\TilePermutator.cpp" />
<ClCompile Include="WFC++\Vector2i.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
Expand Down
9 changes: 6 additions & 3 deletions WFC++.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@
<ClInclude Include="WFC++\xoshiro.hpp">
<Filter>Helper Classes</Filter>
</ClInclude>
<ClInclude Include="WFC++\MemoryChecks.h">
<Filter>Helper Classes</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Helper Classes">
Expand All @@ -98,9 +101,6 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="WFC++\Vector2i.cpp">
<Filter>Helper Classes</Filter>
</ClCompile>
<ClCompile Include="WFC++\Simple\InputData.cpp">
<Filter>Generator Classes\Simple</Filter>
</ClCompile>
Expand Down Expand Up @@ -131,5 +131,8 @@
<ClCompile Include="WFC++\Tiled3D\StandardRunner.cpp">
<Filter>Generator Classes\Tiled3D</Filter>
</ClCompile>
<ClCompile Include="WFC++\HelperSrc\Vector2i.cpp">
<Filter>Helper Classes</Filter>
</ClCompile>
</ItemGroup>
</Project>
47 changes: 45 additions & 2 deletions WFC++/HelperClasses.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,50 @@

namespace WFC
{
//We could use a custom RNG that's much more lightweight,
// but WFC only samples the RNG like twice per iteration.
//Keep in mind WFC only samples the RNG like twice per iteration.
//We don't need something really tight like PCG.
using PRNG = xoshiro256starstar64;


//Unfortunately, std::discrete_distribution is really slow to create,
// and its allocations seemingly can't be re-used,
// so below is my own implementation.

//Returns a random index from the 'weights' list, using its elements as random weights.
//Returns -1 if there are no elements or your weights don't sum to a positive number.
template<typename Weight, typename Rng>
int PickWeightedRandomIndex(Rng& rng, List<Weight>& weights, Weight cachedTotalWeight = -1)
{
if (weights.GetSize() < 1)
return -1;

//Generate a random float from 0 to the total weight, then use that as a "budget"
// to search for the last element before that budget is exhausted.
//There are multiple faster algorithms to do this,
// but they're all harder to implement and I don't think this is a bottleneck.

Weight totalWeight;
if (cachedTotalWeight > Weight{ 0 })
totalWeight = cachedTotalWeight;
else
{
totalWeight = { 0 };
for (const Weight& w : weights)
totalWeight += w;

if (totalWeight <= Weight{ 0 })
return -1;
}

Weight remainingBudget = std::uniform_real_distribution<Weight>(0, totalWeight)(rng);
//Due to floating-point error we may run off the end of the weights array;
// plus once we pass the second-to-last element we already know what the answer will be.
for (int i = 0; i < weights.GetSize() - 1; ++i)
{
remainingBudget -= weights[i];
if (remainingBudget <= 0)
return i;
}
return weights.GetSize() - 1;
}
}
2 changes: 1 addition & 1 deletion WFC++/Vector2i.cpp → WFC++/HelperSrc/Vector2i.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "Vector2i.h"
#include "../Vector2i.h"

using namespace WFC;

Expand Down
37 changes: 11 additions & 26 deletions WFC++/List.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,8 @@ namespace WFC
List() { }

explicit List(int size) : vec(size) { }
explicit List(const T& first) { vec.push_back(first); }
explicit List(const T& first, const T& second) { vec.push_back(first); vec.push_back(second); }
explicit List(const T& first, const T& second, const T& third)
{ vec.push_back(first); vec.push_back(second); vec.push_back(third); }
explicit List(const T& first, const T& second, const T& third, const T& fourth)
{ vec.push_back(first); vec.push_back(second); vec.push_back(third); vec.push_back(fourth); }
List(std::initializer_list<T> list) : vec(list) { }

//Move operators:
List(List<T>&& from) : vec(std::move(from.vec)) { }
List<T>& operator=(List<T>&& from)
{
vec = std::move(from.vec);
return *this;
}
//Copy operators:
List(const List<T>& from) : vec(from.vec) { }
List<T>& operator=(const List<T>& from)
{
vec = from.vec;
return *this;
}


size_t GetSize() const { return vec.size(); }

Expand All @@ -51,19 +30,21 @@ namespace WFC
void Reserve(size_t n) { vec.reserve(n); }

//Adds the given value to the end of this vector.
void PushBack(const T& value) { vec.push_back(value); }
T& PushBack(const T& value) { vec.push_back(value); return vec.back(); }
//Adds the given value to the beginning of this vector.
void PushFront(const T& value) { Insert(0, value); }
T& PushFront(const T& value) { return Insert(0, value); }

T PopBack() { T val = std::move(vec.back()); vec.pop_back(); return val; }
T PopFront() { T val = std::move(vec[0]); vec.erase(vec.begin()); return val; }
void RemoveAt(size_t i) { vec.erase(vec.begin() + i); }
void RemoveAt(size_t i) { vec.erase(std::next(vec.begin(), i)); }

void Insert(size_t i, const T& value) { vec.insert(vec.begin() + i, value); }
void Insert(size_t i, const List<T>& elements) { vec.insert(vec.begin() + i, elements.begin(), elements.end()); }
T& Insert(size_t i, const T& value) { return *vec.insert(vec.begin() + i, value); }
T& Insert(size_t i, const List<T>& elements) { return *vec.insert(vec.begin() + i, elements.begin(), elements.end()); }

void Clear() { vec.clear(); }

bool Contains(const T& t) { return std::find(begin(), end(), t) != end(); }

template<typename Predicate>
int IndexOf(Predicate p) const
{
Expand All @@ -87,6 +68,10 @@ namespace WFC
typename std::vector<T>::const_iterator end() const { return vec.end(); }
typename std::vector<T>::iterator end() { return vec.end(); }

//Needed for performant algorithms.
auto& GetUnderlying() { return vec; }
const auto& GetUnderlying() const { return vec; }

private:

std::vector<T> vec;
Expand Down
103 changes: 103 additions & 0 deletions WFC++/MemoryChecks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#pragma once

#include <cstdint>
#include <array>
#include <inttypes.h>
#include <exception>


//To debug heap memory corruption, `#define WFCPP_CHECK_MEMORY 1` before including this library.
//You may also define a printf-like error handler, `WFCPP_CHECK_MEMORY_ERROR(s, ...)`.
//It defaults to `fprintf(stderr, ...)`.
//Then you can call `DEBUGMEM_Validate()` on an instance to check the padding bits for corruption.

//DEBUG: Turn memory check off explicitly. Associated static_assert is further down.
#define WFCPP_CHECK_MEMORY 0

//For Intellisense, act as if memory debugging is always on.
#if !defined(WFCPP_CHECK_MEMORY) && (defined(__INTELLISENSE__) || defined(__RESHARPER__))
#define WFCPP_CHECK_MEMORY 1
#endif

//By default, disable memory debugging.
#if !defined(WFCPP_CHECK_MEMORY)
#if defined(NDEBUG)
#define WFCPP_CHECK_MEMORY 0
#else
#define WFCPP_CHECK_MEMORY 1
#endif
#endif

//Make sure there is an error handler.
#if WFCPP_CHECK_MEMORY && !defined(WFCPP_CHECK_MEMORY_ERROR)
#define WFCPP_CHECK_MEMORY_ERROR(s, ...) \
fprintf(stderr, "WFCPP: heap corruption detected! " s, ##__VA_ARGS__)
#endif

static_assert(!WFCPP_CHECK_MEMORY);

namespace WFC::DEBUGMEM
{
//Helper struct representing a set of bytes that should stay at a hard-coded value
// unless the heap has been corrupted.
template<size_t N, const char* (*HeaderMsgGetter)()>
struct Padding
{
std::array<std::uint32_t, N> Data;
static constexpr uint32_t TestValue = 0xdeadbeef;
Padding()
{
for (size_t i = 0; i < N; ++i)
Data[i] = TestValue;
}
//Returns -1 if none was found.
int64_t GetFirstFailedIndex() const
{
for (size_t i = 0; i < N; ++i)
if (Data[i] != TestValue)
return static_cast<int64_t>(i);
return -1;
}
void Validate() const
{
auto firstFailedIdx = GetFirstFailedIndex();
if (firstFailedIdx >= 0)
{
WFCPP_CHECK_MEMORY_ERROR(
"Start of %s has corrupted padding value starting at byte %" PRId64 "! "
"It should be %#010x but it's %#010x",
HeaderMsgGetter(), firstFailedIdx * 4,
TestValue, Data[firstFailedIdx]
);
throw std::exception{ };
}
}
};

inline const char* TestPaddingHeaderGetter() { return "TestHere"; }
using TestPadding = Padding<16, &TestPaddingHeaderGetter>;
static_assert(
sizeof(TestPadding) == 16 * 4,
"16" "-element padding doesn't work in this compiler; it gets padded to be larger"
);
}

#if WFCPP_CHECK_MEMORY
//Insert this at the top of a struct to pad it with int32's, to check for bad writes.
//Must be followed up with the FOOTER version at the bottom of the struct.
#define WFCPP_MEMORY_CHECK_HEADER(nWordsOfPadding, headerString) \
static const char* DEBUGMEM_GetHeaderString() { return headerString; } \
WFC::DEBUGMEM::Padding<(nWordsOfPadding), &DEBUGMEM_GetHeaderString> DEBUGMEM_Header; \
static_assert(sizeof(decltype(DEBUGMEM_Header) )== 4 * (nWordsOfPadding), \
#nWordsOfPadding "-element padding doesn't work; it got padded to some larger size")
//Insert this at the bottom of a struct that had the HEADER version inserted to the top of it.
#define WFCPP_MEMORY_CHECK_FOOTER(nWordsOfPadding) \
WFC::DEBUGMEM::Padding<(nWordsOfPadding), &DEBUGMEM_GetHeaderString> DEBUGMEM_Footer; \
static_assert(sizeof(decltype(DEBUGMEM_Footer)) == 4 * (nWordsOfPadding), \
#nWordsOfPadding "-element padding doesn't work; it got padded to some larger size"); \
void DEBUGMEM_Validate() const { DEBUGMEM_Header.Validate(); DEBUGMEM_Footer.Validate(); }
#else
#define WFCPP_MEMORY_CHECK_HEADER(...)
#define WFCPP_MEMORY_CHECK_FOOTER(...) \
template<typename... Args> void DEBUGMEM_Validate(Args... args) const { }
#endif
Loading

0 comments on commit f6bc489

Please sign in to comment.