Browse Source

Turning of memory mutex outside of main and preventing multi-threading during global construction and destruction.

David Piuva 10 months ago
parent
commit
3e2b9f3b0d

+ 12 - 2
Source/DFPSR/api/fileAPI.h

@@ -152,11 +152,21 @@ namespace dsr {
 	#ifdef USE_MICROSOFT_WINDOWS
 	#ifdef USE_MICROSOFT_WINDOWS
 		#define DSR_MAIN_CALLER(MAIN_NAME) \
 		#define DSR_MAIN_CALLER(MAIN_NAME) \
 			void MAIN_NAME(dsr::List<dsr::String> args); \
 			void MAIN_NAME(dsr::List<dsr::String> args); \
-			int main() { MAIN_NAME(dsr::file_impl_getInputArguments()); return 0; }
+			int main() { \
+				dsr::heap_startingApplication(); \
+				MAIN_NAME(dsr::file_impl_getInputArguments()); \
+				dsr::heap_terminatingApplication(); \
+				return 0; \
+			}
 	#else
 	#else
 		#define DSR_MAIN_CALLER(MAIN_NAME) \
 		#define DSR_MAIN_CALLER(MAIN_NAME) \
 			void MAIN_NAME(dsr::List<dsr::String> args); \
 			void MAIN_NAME(dsr::List<dsr::String> args); \
-			int main(int argc, char **argv) { MAIN_NAME(dsr::file_impl_convertInputArguments(argc, (void**)argv)); return 0; }
+			int main(int argc, char **argv) { \
+				dsr::heap_startingApplication(); \
+				MAIN_NAME(dsr::file_impl_convertInputArguments(argc, (void**)argv)); \
+				dsr::heap_terminatingApplication(); \
+				return 0; \
+			}
 	#endif
 	#endif
 	// Helper functions have to be exposed for the macro handle your input arguments.
 	// Helper functions have to be exposed for the macro handle your input arguments.
 	//   Do not call these yourself.
 	//   Do not call these yourself.

+ 46 - 46
Source/DFPSR/api/stringAPI.h

@@ -158,6 +158,52 @@ public:
 	virtual ~Printable();
 	virtual ~Printable();
 };
 };
 
 
+// Used to generate fixed size ascii strings, which is useful when heap allocations are not possible
+//   or you need a safe format until you know which encoding a system call needs to support Unicode.
+template <intptr_t SIZE>
+struct FixedAscii {
+	char characters[SIZE];
+	// Create a fixed size ascii string from a null terminated ascii string.
+	// Crops if text is too long.
+	FixedAscii(const char *text) {
+		bool terminated = false;
+		for (intptr_t i = 0; i < SIZE - 1; i++) {
+			char c = text[i];
+			if (c == '\0') {
+				terminated = true;
+			}
+			if (terminated) {
+				this->characters[i] = '\0';
+			} else if (c > 127) {
+				this->characters[i] = '?';
+			} else {
+				this->characters[i] = c;
+			}
+		}
+		this->characters[SIZE - 1] = '\0';
+	}
+	FixedAscii(const ReadableString &text) {
+		bool terminated = false;
+		for (intptr_t i = 0; i < SIZE - 1; i++) {
+			char c = text[i];
+			if (c == '\0') {
+				terminated = true;
+			}
+			if (terminated) {
+				this->characters[i] = '\0';
+			} else if (c > 127) {
+				this->characters[i] = '?';
+			} else {
+				this->characters[i] = c;
+			}
+		}
+		this->characters[SIZE - 1] = '\0';
+	}
+	const char * getPointer() const {
+		return characters;
+	}
+};
+
 // Helper functions to resolve ambiguity without constexpr if statements in C++ 14.
 // Helper functions to resolve ambiguity without constexpr if statements in C++ 14.
 String& impl_toStreamIndented_ascii(String& target, const char *value, const ReadableString& indentation);
 String& impl_toStreamIndented_ascii(String& target, const char *value, const ReadableString& indentation);
 String& impl_toStreamIndented_utf32(String& target, const char32_t *value, const ReadableString& indentation);
 String& impl_toStreamIndented_utf32(String& target, const char32_t *value, const ReadableString& indentation);
@@ -567,52 +613,6 @@ void printText(ARGS... args) {
 	}
 	}
 #endif
 #endif
 
 
-// Used to generate fixed size ascii strings, which is useful when heap allocations are not possible
-//   or you need a safe format until you know which encoding a system call needs to support Unicode.
-template <intptr_t SIZE>
-struct FixedAscii {
-	char characters[SIZE];
-	// Create a fixed size ascii string from a null terminated ascii string.
-	// Crops if text is too long.
-	FixedAscii(const char *text) {
-		bool terminated = false;
-		for (intptr_t i = 0; i < SIZE - 1; i++) {
-			char c = text[i];
-			if (c == '\0') {
-				terminated = true;
-			}
-			if (terminated) {
-				this->characters[i] = '\0';
-			} else if (c > 127) {
-				this->characters[i] = '?';
-			} else {
-				this->characters[i] = c;
-			}
-		}
-		this->characters[SIZE - 1] = '\0';
-	}
-	FixedAscii(const ReadableString &text) {
-		bool terminated = false;
-		for (intptr_t i = 0; i < SIZE - 1; i++) {
-			char c = text[i];
-			if (c == '\0') {
-				terminated = true;
-			}
-			if (terminated) {
-				this->characters[i] = '\0';
-			} else if (c > 127) {
-				this->characters[i] = '?';
-			} else {
-				this->characters[i] = c;
-			}
-		}
-		this->characters[SIZE - 1] = '\0';
-	}
-	operator const char *() const {
-		return characters;
-	}
-};
-
 }
 }
 
 
 #endif
 #endif

+ 1 - 1
Source/DFPSR/base/SafePointer.cpp

@@ -137,7 +137,7 @@ using namespace dsr;
 		string_append(*target, U"|  Pointer name: ", pointerName, U"\n");
 		string_append(*target, U"|  Pointer name: ", pointerName, U"\n");
 		#ifdef EXTRA_SAFE_POINTER_CHECKS
 		#ifdef EXTRA_SAFE_POINTER_CHECKS
 			if (pointerHeader != nullptr) {
 			if (pointerHeader != nullptr) {
-				string_append(*target, U"|  Allocation name    : ", allocationName, U"\n");
+				string_append(*target, U"|  Allocation name    : ", allocationName.getPointer(), U"\n");
 				string_append(*target, U"|  Thread hash:\n");
 				string_append(*target, U"|  Thread hash:\n");
 				if (headerHash == ANY_THREAD_HASH) {
 				if (headerHash == ANY_THREAD_HASH) {
 					string_append(*target, U"|    Shared with all threads\n");
 					string_append(*target, U"|    Shared with all threads\n");

+ 174 - 128
Source/DFPSR/base/heap.cpp

@@ -21,17 +21,122 @@
 //    3. This notice may not be removed or altered from any source
 //    3. This notice may not be removed or altered from any source
 //    distribution.
 //    distribution.
 
 
+// TODO: Apply thread safety to more memory operations.
+//       heap_getUsedSize and heap_setUsedSize are often used together, forming a transaction without any mutex.
+
 #include "heap.h"
 #include "heap.h"
 #include "../api/stringAPI.h"
 #include "../api/stringAPI.h"
-#include <mutex>
-#include <thread>
+#include "../api/timeAPI.h"
 #include <stdio.h>
 #include <stdio.h>
 #include <new>
 #include <new>
 
 
 // Get settings from here.
 // Get settings from here.
 #include "../settings.h"
 #include "../settings.h"
 
 
+#ifndef DISABLE_MULTI_THREADING
+	// Requires -pthread for linking
+	#include <thread>
+	#include <mutex>
+#endif
+
 namespace dsr {
 namespace dsr {
+	// Keep track of the program's state.
+	enum class ProgramState {
+		Starting, // A single thread does global construction without using any mutex.
+		Running, // Any number of threads allocate and free memory.
+		Terminating // A single thread does global destruction without using any mutex.
+	};
+	ProgramState programState = ProgramState::Starting;
+
+	// Count threads.
+	#ifndef DISABLE_MULTI_THREADING
+		static uint64_t threadCount = 0u;
+		static std::mutex threadLock;
+		struct ThreadCounter {
+			ThreadCounter() {
+				threadLock.lock();
+					threadCount++;
+					if (threadCount > 1 && programState != ProgramState::Running) {
+						if (programState == ProgramState::Starting) {
+							throwError(U"Tried to create another thread before construction of global variables was complete!\n");
+						} else if (programState == ProgramState::Terminating) {
+							throwError(U"Tried to create another thread after destruction of global variables had begun!\n");
+						}
+					}
+				threadLock.unlock();
+			};
+			~ThreadCounter() {
+				threadLock.lock();
+					threadCount--;
+				threadLock.unlock();
+			};
+		};
+		thread_local ThreadCounter threadCounter;
+		static uint64_t getThreadCount() {
+			uint64_t result = 0;
+			threadLock.lock();
+				result = threadCount;
+			threadLock.unlock();
+			return result;
+		}
+	#endif
+
+	// Because locking is recursive, it is safest to just have one global mutex for allocating, freeing and manipulating use counters.
+	//   Otherwise each use counter would need to store the thread identity and recursive depth for each heap allocation.
+	#ifndef DISABLE_MULTI_THREADING
+		static thread_local intptr_t lockDepth = 0;
+		static std::mutex memoryLock;
+	#endif
+
+	inline void lockMemory() {
+		#ifndef DISABLE_MULTI_THREADING
+			// Only call the mutex within main.
+			if (programState == ProgramState::Running) {
+				if (lockDepth == 0) {
+					memoryLock.lock();
+				}
+				lockDepth++;
+			}
+		#endif
+	}
+
+	inline void unlockMemory() {
+		#ifndef DISABLE_MULTI_THREADING
+			// Only call the mutex within main.
+			if (programState == ProgramState::Running) {
+				lockDepth--;
+				assert(lockDepth >= 0);
+				if (lockDepth == 0) {
+					memoryLock.unlock();
+				}
+			}
+		#endif
+	}
+
+	// Called before main, after global initiation completes.
+	void heap_startingApplication() {
+		// Once global initiation has completed, mutexes can be used for multi-threading.
+		programState = ProgramState::Running;
+	}
+
+	// Called after main, before global termination begins.
+	void heap_terminatingApplication() {
+		#ifndef DISABLE_MULTI_THREADING
+			// Wait for all other threads to terminate before closing the program.
+			while (getThreadCount() > 1) {
+				// Sleep for 10 milliseconds before trying again.
+				time_sleepSeconds(0.01);
+			}
+		#endif
+		// Once global termination begins, we can no longer use the mutex.
+		//   The memory system will still get calls to free resources, which must be handled with a single thread.
+		programState = ProgramState::Terminating;
+	}
+
+	// The total number of used heap allocations, excluding recycled memory.
+	// Only accessed when defaultHeap.poolLock is locked.
+	static intptr_t allocationCount = 0;
+
 	using HeapFlag = uint16_t;
 	using HeapFlag = uint16_t;
 	using BinIndex = uint16_t;
 	using BinIndex = uint16_t;
 
 
@@ -39,11 +144,6 @@ namespace dsr {
 	static const uintptr_t heapAlignment = DSR_MAXIMUM_ALIGNMENT;
 	static const uintptr_t heapAlignment = DSR_MAXIMUM_ALIGNMENT;
 	static const uintptr_t heapAlignmentAndMask = memory_createAlignmentAndMask(heapAlignment);
 	static const uintptr_t heapAlignmentAndMask = memory_createAlignmentAndMask(heapAlignment);
 
 
-	// Because locking is recursive, it is safest to just have one global mutex for allocating, freeing and manipulating use counters.
-	//   Otherwise each use counter would need to store the thread identity and recursive depth for each heap allocation.
-	static thread_local intptr_t lockDepth = 0;
-	std::mutex memoryLock;
-
 	// The free function is hidden, because all allocations are forced to use reference counting,
 	// The free function is hidden, because all allocations are forced to use reference counting,
 	//   so that a hard exit can disable recursive calls to heap_free by incrementing all reference counters.
 	//   so that a hard exit can disable recursive calls to heap_free by incrementing all reference counters.
 	static void heap_free(void * const allocation);
 	static void heap_free(void * const allocation);
@@ -60,8 +160,6 @@ namespace dsr {
 			p++;
 			p++;
 		}
 		}
 	}
 	}
-	// TODO: Leave a few bins empty in the beginning until reaching heapAlignment from a minimum alignment.
-	static const int LOWEST_BIN_INDEX = 0;
 	static const int MAX_BIN_COUNT = calculateBinCount();
 	static const int MAX_BIN_COUNT = calculateBinCount();
 
 
 	inline uintptr_t getBinSize(BinIndex index) {
 	inline uintptr_t getBinSize(BinIndex index) {
@@ -72,7 +170,6 @@ namespace dsr {
 		for (intptr_t p = 0; p < MAX_BIN_COUNT; p++) {
 		for (intptr_t p = 0; p < MAX_BIN_COUNT; p++) {
 			uintptr_t result = getBinSize(p);
 			uintptr_t result = getBinSize(p);
 			if (result >= minimumSize) {
 			if (result >= minimumSize) {
-				//printf("getBinIndex %i from %i\n", (int)p, (int)minimumSize);
 				return p;
 				return p;
 			}
 			}
 		}
 		}
@@ -116,18 +213,13 @@ namespace dsr {
 			}
 			}
 		}
 		}
 		inline uintptr_t setUsedSize(uintptr_t size) {
 		inline uintptr_t setUsedSize(uintptr_t size) {
-			//printf("setUsedSize: try %i\n", (int)size);
-			//printf("  MAX_BIN_COUNT: %i\n", (int)MAX_BIN_COUNT);
 			if (!(this->isRecycled())) {
 			if (!(this->isRecycled())) {
 				uintptr_t allocationSize = getAllocationSize();
 				uintptr_t allocationSize = getAllocationSize();
-				//printf("  binIndex: %i\n", (int)this->binIndex);
-				//printf("  allocationSize: %i\n", (int)allocationSize);
 				if (size > allocationSize) {
 				if (size > allocationSize) {
-					//printf("  too big!\n");
+					// Failed to assign the size, so it is important to check the result.
 					size = allocationSize;
 					size = allocationSize;
 				}
 				}
 				this->usedSize = size;
 				this->usedSize = size;
-				//printf("  assigned size: %i\n", (int)this->usedSize);
 				return size;
 				return size;
 			} else {
 			} else {
 				return 0;
 				return 0;
@@ -197,7 +289,6 @@ namespace dsr {
 		if (allocation != nullptr) {
 		if (allocation != nullptr) {
 			HeapHeader *header = headerFromAllocation(allocation);
 			HeapHeader *header = headerFromAllocation(allocation);
 			result = header->getUsedSize();
 			result = header->getUsedSize();
-			//printf("  heap_getUsedSize: get %i\n", (int)result);
 		}
 		}
 		return result;
 		return result;
 	}
 	}
@@ -205,10 +296,8 @@ namespace dsr {
 	uintptr_t heap_setUsedSize(void * const allocation, uintptr_t size) {
 	uintptr_t heap_setUsedSize(void * const allocation, uintptr_t size) {
 		uintptr_t result = 0;
 		uintptr_t result = 0;
 		if (allocation != nullptr) {
 		if (allocation != nullptr) {
-			//uintptr_t allocationSize = heap_getAllocationSize(allocation);
 			HeapHeader *header = headerFromAllocation(allocation);
 			HeapHeader *header = headerFromAllocation(allocation);
 			result = header->setUsedSize(size);
 			result = header->setUsedSize(size);
-			//printf("  heap_setUsedSize: try %i get %i\n", (int)size, (int)result);
 		}
 		}
 		return result;
 		return result;
 	}
 	}
@@ -216,39 +305,25 @@ namespace dsr {
 	void heap_increaseUseCount(void const * const allocation) {
 	void heap_increaseUseCount(void const * const allocation) {
 		if (allocation != nullptr) {
 		if (allocation != nullptr) {
 			HeapHeader *header = headerFromAllocation(allocation);
 			HeapHeader *header = headerFromAllocation(allocation);
-			if (lockDepth == 0) memoryLock.lock();
-			//printf("heap_increaseUseCount called for allocation @ %ld\n", (uintptr_t)allocation);
-			//printf("    Use count: %ld -> %ld\n", header->useCount, header->useCount + 1);
-			//#ifdef SAFE_POINTER_CHECKS
-			//	printf("    ID: %lu\n", header->allocationIdentity);
-			//	printf("    Name: %s\n", header->name ? header->name : "<nameless>");
-			//#endif
+			lockMemory();
 			header->useCount++;
 			header->useCount++;
-			if (lockDepth == 0) memoryLock.unlock();
+			unlockMemory();
 		}
 		}
 	}
 	}
 
 
 	void heap_decreaseUseCount(void const * const allocation) {
 	void heap_decreaseUseCount(void const * const allocation) {
 		if (allocation != nullptr) {
 		if (allocation != nullptr) {
 			HeapHeader *header = headerFromAllocation(allocation);
 			HeapHeader *header = headerFromAllocation(allocation);
-			if (lockDepth == 0) memoryLock.lock();
-			//printf("heap_decreaseUseCount called for allocation @ %ld\n", (uintptr_t)allocation);
-			//printf("    Use count: %ld -> %ld\n", header->useCount, header->useCount - 1);
-			//#ifdef SAFE_POINTER_CHECKS
-			//	printf("    ID: %lu\n", header->allocationIdentity);
-			//	printf("    Name: %s\n", header->name ? header->name : "<nameless>");
-			//#endif
+			lockMemory();
 			if (header->useCount == 0) {
 			if (header->useCount == 0) {
 				throwError(U"Heap error: Decreasing a count that is already zero!\n");
 				throwError(U"Heap error: Decreasing a count that is already zero!\n");
 			} else {
 			} else {
 				header->useCount--;
 				header->useCount--;
 				if (header->useCount == 0) {
 				if (header->useCount == 0) {
-					lockDepth++;
 					heap_free((void*)allocation);
 					heap_free((void*)allocation);
-					lockDepth--;
 				}
 				}
 			}
 			}
-			if (lockDepth == 0) memoryLock.unlock();
+			unlockMemory();
 		}
 		}
 	}
 	}
 
 
@@ -277,15 +352,10 @@ namespace dsr {
 		}
 		}
 	};
 	};
 
 
-	// The total number of used heap allocations, excluding recycled memory.
-	// Only accessed when defaultHeap.poolLock is locked.
-	static intptr_t allocationCount = 0;
-
 	// The heap can have memory freed after its own destruction by telling the remaining allocations to clean up after themselves.
 	// The heap can have memory freed after its own destruction by telling the remaining allocations to clean up after themselves.
 	struct HeapPool {
 	struct HeapPool {
 		HeapMemory *lastHeap = nullptr;
 		HeapMemory *lastHeap = nullptr;
 		HeapHeader *recyclingBin[MAX_BIN_COUNT] = {};
 		HeapHeader *recyclingBin[MAX_BIN_COUNT] = {};
-		bool terminating = false;
 		void cleanUp() {
 		void cleanUp() {
 			// If memory safety checks are enabled, then we should indicate that everything is fine with the memory once cleaning up.
 			// If memory safety checks are enabled, then we should indicate that everything is fine with the memory once cleaning up.
 			//   There is however no way to distinguish between leaking memory and not yet having terminated everything, so there is no leak warning to print.
 			//   There is however no way to distinguish between leaking memory and not yet having terminated everything, so there is no leak warning to print.
@@ -303,12 +373,10 @@ namespace dsr {
 		}
 		}
 		HeapPool() {}
 		HeapPool() {}
 		~HeapPool() {
 		~HeapPool() {
-			memoryLock.lock();
-				this->terminating = true;
-				if (allocationCount == 0) {
-					this->cleanUp();
-				}
-			memoryLock.unlock();
+			// The destruction should be ignored to manually terminate once all memory allocations have been freed.
+			if (programState != ProgramState::Terminating) {
+				throwError(U"Heap error: Terminated the application without first calling heap_terminatingApplication or heap_hardExitCleaning!\n");
+			}
 		}
 		}
 	};
 	};
 
 
@@ -354,35 +422,32 @@ namespace dsr {
 	UnsafeAllocation heap_allocate(uintptr_t minimumSize, bool zeroed) {
 	UnsafeAllocation heap_allocate(uintptr_t minimumSize, bool zeroed) {
 		UnsafeAllocation result(nullptr, nullptr);
 		UnsafeAllocation result(nullptr, nullptr);
 		int32_t binIndex = getBinIndex(minimumSize);
 		int32_t binIndex = getBinIndex(minimumSize);
-		//printf("heap_allocate\n");
-		//printf("  minimumSize: %i\n", (int)minimumSize);
-		//printf("  binIndex: %i\n", (int)binIndex);
 		if (binIndex == -1) {
 		if (binIndex == -1) {
 			// If the requested allocation is so big that there is no power of two that can contain it without overflowing the address space, then it can not be allocated.
 			// If the requested allocation is so big that there is no power of two that can contain it without overflowing the address space, then it can not be allocated.
 			throwError(U"Heap error: Exceeded the maximum size when trying to allocate!\n");
 			throwError(U"Heap error: Exceeded the maximum size when trying to allocate!\n");
 		} else {
 		} else {
 			uintptr_t paddedSize = ((uintptr_t)1 << binIndex) * heapAlignment;
 			uintptr_t paddedSize = ((uintptr_t)1 << binIndex) * heapAlignment;
-			if (lockDepth == 0) memoryLock.lock();
-			allocationCount++;
-			// Look for pre-existing allocations in the recycling bins.
-			HeapHeader *binHeader = defaultHeap.recyclingBin[binIndex];
-			if (binHeader != nullptr) {
-				// Make the recycled allocation's tail into the new head.
-				defaultHeap.recyclingBin[binIndex] = binHeader->nextRecycled;
-				// Clear the pointer to make room for the allocation's size in the union.
-				binHeader->nextRecycled = nullptr;
-				// Mark the allocation as not recycled. (assume that it was recycled when found in the bin)
-				binHeader->makeUsed();
-				binHeader->reuse(false, "Nameless reused allocation");
-				result = UnsafeAllocation((uint8_t*)allocationFromHeader(binHeader), binHeader);
-			} else {
-				// Look for a heap with enough space for a new allocation.
-				result = tryToAllocate(defaultHeap, paddedSize, heapAlignmentAndMask);
-				if (result.data == nullptr) {
-					throwError(U"Heap error: Failed to allocate more memory!\n");
+			lockMemory();
+				allocationCount++;
+				// Look for pre-existing allocations in the recycling bins.
+				HeapHeader *binHeader = defaultHeap.recyclingBin[binIndex];
+				if (binHeader != nullptr) {
+					// Make the recycled allocation's tail into the new head.
+					defaultHeap.recyclingBin[binIndex] = binHeader->nextRecycled;
+					// Clear the pointer to make room for the allocation's size in the union.
+					binHeader->nextRecycled = nullptr;
+					// Mark the allocation as not recycled. (assume that it was recycled when found in the bin)
+					binHeader->makeUsed();
+					binHeader->reuse(false, "Nameless reused allocation");
+					result = UnsafeAllocation((uint8_t*)allocationFromHeader(binHeader), binHeader);
+				} else {
+					// Look for a heap with enough space for a new allocation.
+					result = tryToAllocate(defaultHeap, paddedSize, heapAlignmentAndMask);
+					if (result.data == nullptr) {
+						throwError(U"Heap error: Failed to allocate more memory!\n");
+					}
 				}
 				}
-			}
-			if (lockDepth == 0) memoryLock.unlock();
+			unlockMemory();
 			if (zeroed && result.data != nullptr) {
 			if (zeroed && result.data != nullptr) {
 				memset(result.data, 0, paddedSize);
 				memset(result.data, 0, paddedSize);
 			}
 			}
@@ -394,12 +459,6 @@ namespace dsr {
 			head->binIndex = binIndex;
 			head->binIndex = binIndex;
 			// Tell the allocation how many of the bytes that are used.
 			// Tell the allocation how many of the bytes that are used.
 			head->setUsedSize(minimumSize);
 			head->setUsedSize(minimumSize);
-			// Give a debug name to the allocation if we are debugging.
-			//printf("Allocated memory @ %ld\n", (uintptr_t)result.data);
-			//printf("    Use count = %ld\n", head->useCount);
-			//#ifdef SAFE_POINTER_CHECKS
-			//	printf("    ID = %lu\n", head->allocationIdentity);
-			//#endif
 		}
 		}
 		return result;
 		return result;
 	}
 	}
@@ -410,57 +469,45 @@ namespace dsr {
 	}
 	}
 
 
 	static void heap_free(void * const allocation) {
 	static void heap_free(void * const allocation) {
-		if (lockDepth == 0) memoryLock.lock();
-		// Get the recycled allocation's header.
-		HeapHeader *header = headerFromAllocation(allocation);
-		if (header->isRecycled()) {
-			throwError(U"Heap error: A heap allocation was freed twice!\n");
-		} else {
-			// Call the destructor without using the mutex (lockDepth > 0).
-			lockDepth++;
-			//printf("heap_free called for allocation @ %ld\n", (uintptr_t)allocation);
-			//printf("    Use count: %ld\n", header->useCount);
-			//#ifdef SAFE_POINTER_CHECKS
-			//	printf("    ID: %lu\n", header->allocationIdentity);
-			//	printf("    Name: %s\n", header->name ? header->name : "<nameless>");
-			//#endif
-			//printf("    Calling destructor\n");
-			// Call the destructor provided with any external resource that also needs to be freed.
-			if (header->destructor.destructor) {
-				header->destructor.destructor(allocation, header->destructor.externalResource);
-			}
-			//printf("    Finished destructor\n");
-			lockDepth--;
-			assert(lockDepth >= 0);
-			// Remove the destructor so that it is not called again for the next allocation.
-			header->destructor = HeapDestructor();
-			int binIndex = header->binIndex;
-			if (binIndex >= MAX_BIN_COUNT) {
-				throwError(U"Heap error: Out of bound recycling bin index in corrupted head of freed allocation!\n");
+		lockMemory();
+			// Get the recycled allocation's header.
+			HeapHeader *header = headerFromAllocation(allocation);
+			if (header->isRecycled()) {
+				throwError(U"Heap error: A heap allocation was freed twice!\n");
 			} else {
 			} else {
-				// Make any previous head from the bin into the new tail.
-				HeapHeader *oldHeader = defaultHeap.recyclingBin[binIndex];
-				header->nextRecycled = oldHeader;
-				// Mark the allocation as recycled.
-				header->makeRecycled();
-				#ifdef SAFE_POINTER_CHECKS
-					// Remove the allocation identity, so that use of freed memory can be detected in SafePointer and Handle.
-					header->allocationIdentity = 0;
-					header->threadHash = 0;
-				#endif
-				// Store the newly recycled allocation in the bin.
-				defaultHeap.recyclingBin[binIndex] = header;
-				header->nextRecycled = oldHeader;
+				// Call the destructor provided with any external resource that also needs to be freed.
+				if (header->destructor.destructor) {
+					header->destructor.destructor(allocation, header->destructor.externalResource);
+				}
+				// Remove the destructor so that it is not called again for the next allocation.
+				header->destructor = HeapDestructor();
+				int binIndex = header->binIndex;
+				if (binIndex >= MAX_BIN_COUNT) {
+					throwError(U"Heap error: Out of bound recycling bin index in corrupted head of freed allocation!\n");
+				} else {
+					// Make any previous head from the bin into the new tail.
+					HeapHeader *oldHeader = defaultHeap.recyclingBin[binIndex];
+					header->nextRecycled = oldHeader;
+					// Mark the allocation as recycled.
+					header->makeRecycled();
+					#ifdef SAFE_POINTER_CHECKS
+						// Remove the allocation identity, so that use of freed memory can be detected in SafePointer and Handle.
+						header->allocationIdentity = 0;
+						header->threadHash = 0;
+					#endif
+					// Store the newly recycled allocation in the bin.
+					defaultHeap.recyclingBin[binIndex] = header;
+					header->nextRecycled = oldHeader;
+				}
 			}
 			}
-		}
-		// By decreasing the count after recursive calls to destructors, we can make sure that the arena is freed last.
-		// If a destructor allocates new memory, it will have to allocate a new arena and then clean it up again.
-		allocationCount--;
-		// If the heap has been told to terminate and we reached zero allocations, we can tell it to clean up.
-		if (defaultHeap.terminating && allocationCount == 0) {
-			defaultHeap.cleanUp();
-		}
-		if (lockDepth == 0) memoryLock.unlock();
+			// By decreasing the count after recursive calls to destructors, we can make sure that the arena is freed last.
+			// If a destructor allocates new memory, it will have to allocate a new arena and then clean it up again.
+			allocationCount--;
+			// If the heap has been told to terminate and we reached zero allocations, we can tell it to clean up.
+			if (programState == ProgramState::Terminating && allocationCount == 0) {
+				defaultHeap.cleanUp();
+			}
+		unlockMemory();
 	}
 	}
 
 
 	static void forAllHeapAllocations(HeapMemory &heap, std::function<void(AllocationHeader * header, void * allocation)> callback) {
 	static void forAllHeapAllocations(HeapMemory &heap, std::function<void(AllocationHeader * header, void * allocation)> callback) {
@@ -485,12 +532,11 @@ namespace dsr {
 
 
 	void heap_hardExitCleaning() {
 	void heap_hardExitCleaning() {
 		// TODO:
 		// TODO:
-		// * Implement a function that iterates over all heap allocations.
 		// * Increment the use count for each allocation, to prevent recursive freeing of resources.
 		// * Increment the use count for each allocation, to prevent recursive freeing of resources.
 		// * Call the destructor on each allocation without freeing any memory, while all memory is still available.
 		// * Call the destructor on each allocation without freeing any memory, while all memory is still available.
 		// Then the arenas can safely be deallocated without looking at individual allocations again.
 		// Then the arenas can safely be deallocated without looking at individual allocations again.
 		allocationCount = 0;
 		allocationCount = 0;
-		defaultHeap.terminating = true;
+		programState = ProgramState::Terminating;
 		defaultHeap.cleanUp();
 		defaultHeap.cleanUp();
 	}
 	}
 
 

+ 6 - 0
Source/DFPSR/base/heap.h

@@ -138,6 +138,12 @@ namespace dsr {
 	//   Recycled allocations are not included.
 	//   Recycled allocations are not included.
 	void heap_forAllHeapAllocations(std::function<void(AllocationHeader * header, void * allocation)> callback);
 	void heap_forAllHeapAllocations(std::function<void(AllocationHeader * header, void * allocation)> callback);
 
 
+	// Called by DSR_MAIN_CALLER when the program starts.
+	void heap_startingApplication();
+
+	// Called by DSR_MAIN_CALLER when the program closes.
+	void heap_terminatingApplication();
+
 	// If terminating the program using std::exit, you can call this first to free all heap memory in the allocator, leaked or not.
 	// If terminating the program using std::exit, you can call this first to free all heap memory in the allocator, leaked or not.
 	void heap_hardExitCleaning();
 	void heap_hardExitCleaning();
 
 

+ 34 - 19
Source/DFPSR/base/threading.cpp

@@ -25,20 +25,28 @@
 #include "virtualStack.h"
 #include "virtualStack.h"
 #include "../math/scalar.h"
 #include "../math/scalar.h"
 
 
-// Requires -pthread for linking
-#include <future>
-#include <thread>
-#include <mutex>
-#include <atomic>
+// Get settings from here.
+#include "../settings.h"
+
+#ifndef DISABLE_MULTI_THREADING
+	// Requires -pthread for linking
+	#include <thread>
+	#include <mutex>
+	#include <future>
+#endif
 
 
 namespace dsr {
 namespace dsr {
 
 
-// Enable this macro to disable multi-threading
-//   If your application still crashes when using a single thread, it's probably not a concurrency problem
-//#define DISABLE_MULTI_THREADING
+#ifndef DISABLE_MULTI_THREADING
+	static std::mutex getTaskLock;
+#endif
 
 
 int getThreadCount() {
 int getThreadCount() {
-	return (int)std::thread::hardware_concurrency();
+	#ifndef DISABLE_MULTI_THREADING
+		return (int)std::thread::hardware_concurrency();
+	#else
+		return 1;
+	#endif
 }
 }
 
 
 void threadedWorkFromArray(std::function<void()>* jobs, int jobCount, int maxThreadCount) {
 void threadedWorkFromArray(std::function<void()>* jobs, int jobCount, int maxThreadCount) {
@@ -53,7 +61,6 @@ void threadedWorkFromArray(std::function<void()>* jobs, int jobCount, int maxThr
 		} else if (jobCount == 1) {
 		} else if (jobCount == 1) {
 			jobs[0]();
 			jobs[0]();
 		} else {
 		} else {
-			static std::recursive_mutex getTaskLock;
 			if (maxThreadCount <= 0) {
 			if (maxThreadCount <= 0) {
 				// No limit.
 				// No limit.
 				maxThreadCount = jobCount;
 				maxThreadCount = jobCount;
@@ -117,11 +124,15 @@ void threadedWorkFromList(List<std::function<void()>> jobs, int maxThreadCount)
 }
 }
 
 
 void threadedSplit(int startIndex, int stopIndex, std::function<void(int startIndex, int stopIndex)> task, int minimumJobSize, int jobsPerThread) {
 void threadedSplit(int startIndex, int stopIndex, std::function<void(int startIndex, int stopIndex)> task, int minimumJobSize, int jobsPerThread) {
-	int totalCount = stopIndex - startIndex;
-	int maxJobs = totalCount / minimumJobSize;
-	int jobCount = std::thread::hardware_concurrency() * jobsPerThread;
-	if (jobCount > maxJobs) { jobCount = maxJobs; }
-	if (jobCount < 1) { jobCount = 1; }
+	#ifndef DISABLE_MULTI_THREADING
+		int totalCount = stopIndex - startIndex;
+		int maxJobs = totalCount / minimumJobSize;
+		int jobCount = getThreadCount() * jobsPerThread;
+		if (jobCount > maxJobs) { jobCount = maxJobs; }
+		if (jobCount < 1) { jobCount = 1; }
+	#else
+		int jobCount = 1;
+	#endif
 	if (jobCount == 1) {
 	if (jobCount == 1) {
 		// Too little work for multi-threading
 		// Too little work for multi-threading
 		task(startIndex, stopIndex);
 		task(startIndex, stopIndex);
@@ -149,10 +160,14 @@ void threadedSplit_disabled(int startIndex, int stopIndex, std::function<void(in
 }
 }
 
 
 void threadedSplit(const IRect& bound, std::function<void(const IRect& bound)> task, int minimumRowsPerJob, int jobsPerThread) {
 void threadedSplit(const IRect& bound, std::function<void(const IRect& bound)> task, int minimumRowsPerJob, int jobsPerThread) {
-	int maxJobs = bound.height() / minimumRowsPerJob;
-	int jobCount = std::thread::hardware_concurrency() * jobsPerThread;
-	if (jobCount > maxJobs) { jobCount = maxJobs; }
-	if (jobCount < 1) { jobCount = 1; }
+	#ifndef DISABLE_MULTI_THREADING
+		int maxJobs = bound.height() / minimumRowsPerJob;
+		int jobCount = getThreadCount() * jobsPerThread;
+		if (jobCount > maxJobs) { jobCount = maxJobs; }
+		if (jobCount < 1) { jobCount = 1; }
+	#else
+		int jobCount = 1;
+	#endif
 	if (jobCount == 1) {
 	if (jobCount == 1) {
 		// Too little work for multi-threading
 		// Too little work for multi-threading
 		task(bound);
 		task(bound);

+ 1 - 0
Source/DFPSR/base/virtualStack.cpp

@@ -52,6 +52,7 @@ namespace dsr {
 
 
 	// Additional stacks in heap memory.
 	// Additional stacks in heap memory.
 	struct DynamicStackMemory : public StackMemory {
 	struct DynamicStackMemory : public StackMemory {
+		DynamicStackMemory() {}
 		~DynamicStackMemory() {
 		~DynamicStackMemory() {
 			if (this->top != nullptr) {
 			if (this->top != nullptr) {
 				heap_decreaseUseCount(this->top);
 				heap_decreaseUseCount(this->top);

+ 4 - 0
Source/DFPSR/settings.h

@@ -18,6 +18,10 @@
 	// Has no effect in release mode, because it is only active when SAFE_POINTER_CHECKS is also defined.
 	// Has no effect in release mode, because it is only active when SAFE_POINTER_CHECKS is also defined.
 	//#define EXTRA_SAFE_POINTER_CHECKS
 	//#define EXTRA_SAFE_POINTER_CHECKS
 
 
+	// Enable this macro to disable multi-threading.
+	//   Can be used to quickly rule out concurrency problems when debugging, by recreating the same error without extra threads.
+	//#define DISABLE_MULTI_THREADING
+
 	// Determine which SIMD extensions to use in base/simd.h.
 	// Determine which SIMD extensions to use in base/simd.h.
 	// Use the standard compiler flags for enabling SIMD extensions.
 	// Use the standard compiler flags for enabling SIMD extensions.
 	//   If your compiler uses a different macro name to indicate the presence of a SIMD extension, you can add them here to enable the USE_* macros.
 	//   If your compiler uses a different macro name to indicate the presence of a SIMD extension, you can add them here to enable the USE_* macros.

+ 7 - 5
Source/SDK/sandbox/main.cpp

@@ -1,15 +1,17 @@
 
 
+#include "../../DFPSR/api/fileAPI.h"
+
 // No need for headers when there's just one pre-declaration per module being used once
 // No need for headers when there's just one pre-declaration per module being used once
 void sandbox_main();
 void sandbox_main();
-void tool_main(int argn, char **argv);
+void tool_main(const dsr::List<dsr::String> &args);
 
 
 // Multiple applications in the same binary based on which arguments are given
 // Multiple applications in the same binary based on which arguments are given
-int main(int argn, char **argv) {
-	if (argn > 1) {
-		tool_main(argn, argv);
+DSR_MAIN_CALLER(dsrMain)
+void dsrMain(dsr::List<dsr::String> args) {
+	if (args.length() > 1) {
+		tool_main(args);
 	} else {
 	} else {
 		sandbox_main();
 		sandbox_main();
 	}
 	}
-	return 0;
 }
 }
 
 

+ 2 - 0
Source/SDK/sandbox/media/gen.sh

@@ -1,5 +1,7 @@
 #!/bin/bash
 #!/bin/bash
 
 
+# TODO: Give whole folders and let the application search for the files using the file API.
+
 # Call the application using a list of model generation scripts to pre-render graphics.
 # Call the application using a list of model generation scripts to pre-render graphics.
 #   Ortho.ini defines the game's camera system and should be used by the game when drawing the generated sprites.
 #   Ortho.ini defines the game's camera system and should be used by the game when drawing the generated sprites.
 ../Sandbox ./models ./images ./Ortho.ini Floor WoodenFloor WoodenFence WoodenBarrel Pillar Character_Mage
 ../Sandbox ./models ./images ./Ortho.ini Floor WoodenFloor WoodenFence WoodenBarrel Pillar Character_Mage

+ 7 - 7
Source/SDK/sandbox/tool.cpp

@@ -492,15 +492,15 @@ void processScript(const String& sourcePath, const String& targetPath, OrthoSyst
 // The second argument is the target folder in which the results are saved.
 // The second argument is the target folder in which the results are saved.
 // The third argument is the ortho configuration file path.
 // The third argument is the ortho configuration file path.
 // The following arguments are plain names of the scripts to process without any path nor extension.
 // The following arguments are plain names of the scripts to process without any path nor extension.
-void tool_main(int argn, char **argv) {
-	if (argn < 5) {
+void tool_main(const List<String> &args) {
+	if (args.length() < 5) {
 		printText("Nothing to process. Terminating sprite generation tool.\n");
 		printText("Nothing to process. Terminating sprite generation tool.\n");
 	} else {
 	} else {
-		String sourcePath = string_combine(argv[1], file_separator());
-		String targetPath = string_combine(argv[2], file_separator());
-		OrthoSystem ortho = OrthoSystem(string_load(String(argv[3])));
-		for (int a = 4; a < argn; a++) {
-			processScript(sourcePath, targetPath, ortho, String(argv[a]));
+		String sourcePath = string_combine(args[1], file_separator());
+		String targetPath = string_combine(args[2], file_separator());
+		OrthoSystem ortho = OrthoSystem(string_load(args[3]));
+		for (int a = 4; a < args.length(); a++) {
+			processScript(sourcePath, targetPath, ortho, args[a]);
 		}
 		}
 	}
 	}
 }
 }

+ 1 - 1
Source/test.sh

@@ -52,7 +52,7 @@ for file in ./test/tests/*.cpp; do
 	else
 	else
 		echo "Failed ${name}!";
 		echo "Failed ${name}!";
 		# Re-run with a memory debugger.
 		# Re-run with a memory debugger.
-		#gdb ./${TEMP_DIR}/application;
+		gdb -ex "run" -ex "bt" -ex "quit" --args ./${TEMP_DIR}/application;
 		exit 1
 		exit 1
 	fi
 	fi
 done
 done

+ 11 - 6
Source/test/testTools.h

@@ -58,12 +58,17 @@ static void messageHandler(const ReadableString &message, MessageType type) {
 }
 }
 
 
 #define START_TEST(NAME) \
 #define START_TEST(NAME) \
-int main() { \
-	std::signal(SIGSEGV, [](int signal) { throwError(U"Segmentation fault!"); }); \
-	string_assignMessageHandler(&messageHandler); \
-	printText(U"Running test \"", #NAME, "\": ");
-
-#define END_TEST printText(U" (done)\n"); return PASSED; }
+	int main() { \
+		std::signal(SIGSEGV, [](int signal) { throwError(U"Segmentation fault!"); }); \
+		string_assignMessageHandler(&messageHandler); \
+		heap_startingApplication(); \
+		printText(U"Running test \"", #NAME, "\": ");
+
+#define END_TEST \
+		printText(U" (done)\n"); \
+		heap_terminatingApplication(); \
+		return PASSED; \
+	}
 
 
 #define OP_EQUALS(A, B) ((A) == (B))
 #define OP_EQUALS(A, B) ((A) == (B))
 #define OP_NOT_EQUALS(A, B) ((A) != (B))
 #define OP_NOT_EQUALS(A, B) ((A) != (B))

+ 31 - 25
Source/windowManagers/Win32Window.cpp

@@ -17,13 +17,19 @@ Link to these dependencies for MS Windows:
 #include "../DFPSR/api/timeAPI.h"
 #include "../DFPSR/api/timeAPI.h"
 #include "../DFPSR/gui/BackendWindow.h"
 #include "../DFPSR/gui/BackendWindow.h"
 
 
-#include <mutex>
-#include <future>
+#include "../DFPSR/settings.h"
 
 
-static std::mutex windowLock;
+#ifndef DISABLE_MULTI_THREADING
+	#include <mutex>
+	#include <future>
 
 
-// Enable this macro to disable multi-threading
-//#define DISABLE_MULTI_THREADING
+	static std::mutex windowLock;
+	inline void lockWindow() { windowLock.lock(); }
+	inline void unlockWindow() { windowLock.unlock(); }
+#else
+	inline void lockWindow() {}
+	inline void unlockWindow() {}
+#endif
 
 
 static const int bufferCount = 2;
 static const int bufferCount = 2;
 
 
@@ -154,20 +160,20 @@ void Win32Window::saveToClipboard(const dsr::ReadableString &text, double timeou
 }
 }
 
 
 void Win32Window::updateTitle_locked() {
 void Win32Window::updateTitle_locked() {
-	windowLock.lock();
-		if (!SetWindowTextA(this->hwnd, dsr::FixedAscii<512>(this->title))) {
+	lockWindow();
+		if (!SetWindowTextA(this->hwnd, dsr::FixedAscii<512>(this->title).getPointer())) {
 			dsr::printText("Warning! Could not assign the window title ", dsr::string_mangleQuote(this->title), ".\n");
 			dsr::printText("Warning! Could not assign the window title ", dsr::string_mangleQuote(this->title), ".\n");
 		}
 		}
-	windowLock.unlock();
+	unlockWindow();
 }
 }
 
 
 // The method can be seen as locked, but it overrides a virtual method that is independent of threading.
 // The method can be seen as locked, but it overrides a virtual method that is independent of threading.
 void Win32Window::setCursorPosition(int x, int y) {
 void Win32Window::setCursorPosition(int x, int y) {
-	windowLock.lock();
+	lockWindow();
 		POINT point; point.x = x; point.y = y;
 		POINT point; point.x = x; point.y = y;
 		ClientToScreen(this->hwnd, &point);
 		ClientToScreen(this->hwnd, &point);
 		SetCursorPos(point.x, point.y);
 		SetCursorPos(point.x, point.y);
-	windowLock.unlock();
+	unlockWindow();
 }
 }
 
 
 bool Win32Window::setCursorVisibility(bool visible) {
 bool Win32Window::setCursorVisibility(bool visible) {
@@ -193,23 +199,23 @@ void Win32Window::setFullScreen(bool enabled) {
 }
 }
 
 
 void Win32Window::removeOldWindow_locked() {
 void Win32Window::removeOldWindow_locked() {
-	windowLock.lock();
+	lockWindow();
 		if (this->windowState != 0) {
 		if (this->windowState != 0) {
 			DestroyWindow(this->hwnd);
 			DestroyWindow(this->hwnd);
 		}
 		}
 	this->windowState = 0;
 	this->windowState = 0;
-	windowLock.unlock();
+	unlockWindow();
 }
 }
 
 
 void Win32Window::prepareWindow_locked() {
 void Win32Window::prepareWindow_locked() {
-	windowLock.lock();
+	lockWindow();
 		// Reallocate the canvas
 		// Reallocate the canvas
 		this->resizeCanvas(this->windowWidth, this->windowHeight);
 		this->resizeCanvas(this->windowWidth, this->windowHeight);
 		// Show the window
 		// Show the window
 		ShowWindow(this->hwnd, SW_NORMAL);
 		ShowWindow(this->hwnd, SW_NORMAL);
 		// Repaint
 		// Repaint
 		UpdateWindow(this->hwnd);
 		UpdateWindow(this->hwnd);
-	windowLock.unlock();
+	unlockWindow();
 }
 }
 
 
 static bool registered = false;
 static bool registered = false;
@@ -249,7 +255,7 @@ void Win32Window::createWindowed_locked(const dsr::String& title, int width, int
 	this->windowHeight = height;
 	this->windowHeight = height;
 	this->receivedWindowResize(width, height);
 	this->receivedWindowResize(width, height);
 
 
-	windowLock.lock();
+	lockWindow();
 		// Register the Window class during first creation
 		// Register the Window class during first creation
 		registerIfNeeded();
 		registerIfNeeded();
 
 
@@ -268,7 +274,7 @@ void Win32Window::createWindowed_locked(const dsr::String& title, int width, int
 		  NULL,                // hInstance
 		  NULL,                // hInstance
 		  (LPVOID)this         // lpParam
 		  (LPVOID)this         // lpParam
 		);
 		);
-	windowLock.unlock();
+	unlockWindow();
 
 
 	this->updateTitle_locked();
 	this->updateTitle_locked();
 
 
@@ -277,7 +283,7 @@ void Win32Window::createWindowed_locked(const dsr::String& title, int width, int
 }
 }
 
 
 void Win32Window::createFullscreen_locked() {
 void Win32Window::createFullscreen_locked() {
-	windowLock.lock();
+	lockWindow();
 		int screenWidth = GetSystemMetrics(SM_CXSCREEN);
 		int screenWidth = GetSystemMetrics(SM_CXSCREEN);
 		int screenHeight = GetSystemMetrics(SM_CYSCREEN);
 		int screenHeight = GetSystemMetrics(SM_CYSCREEN);
 
 
@@ -304,7 +310,7 @@ void Win32Window::createFullscreen_locked() {
 		  NULL,                  // hInstance
 		  NULL,                  // hInstance
 		  (LPVOID)this           // lpParam
 		  (LPVOID)this           // lpParam
 		);
 		);
-	windowLock.unlock();
+	unlockWindow();
 
 
 	this->windowState = 2;
 	this->windowState = 2;
 	this->prepareWindow_locked();
 	this->prepareWindow_locked();
@@ -319,7 +325,7 @@ Win32Window::Win32Window(const dsr::String& title, int width, int height) {
 	// Remember the title
 	// Remember the title
 	this->title = title;
 	this->title = title;
 
 
-	windowLock.lock();
+	lockWindow();
 		// Get the default cursor
 		// Get the default cursor
 		this->defaultCursor = LoadCursor(0, IDC_ARROW);
 		this->defaultCursor = LoadCursor(0, IDC_ARROW);
 
 
@@ -327,7 +333,7 @@ Win32Window::Win32Window(const dsr::String& title, int width, int height) {
 		uint32_t cursorAndMask = 0b11111111;
 		uint32_t cursorAndMask = 0b11111111;
 		uint32_t cursorXorMask = 0b00000000;
 		uint32_t cursorXorMask = 0b00000000;
 		this->noCursor = CreateCursor(NULL, 0, 0, 1, 1, (const void*)&cursorAndMask, (const void*)&cursorXorMask);
 		this->noCursor = CreateCursor(NULL, 0, 0, 1, 1, (const void*)&cursorAndMask, (const void*)&cursorXorMask);
-	windowLock.unlock();
+	unlockWindow();
 
 
 	// Create a window
 	// Create a window
 	if (fullScreen) {
 	if (fullScreen) {
@@ -627,7 +633,7 @@ void Win32Window::prefetchEvents() {
 	// Only prefetch new events if nothing else is locking.
 	// Only prefetch new events if nothing else is locking.
 	if (windowLock.try_lock()) {
 	if (windowLock.try_lock()) {
 		this->prefetchEvents_impl();
 		this->prefetchEvents_impl();
-		windowLock.unlock();
+		unlockWindow();
 	}
 	}
 }
 }
 
 
@@ -653,12 +659,12 @@ Win32Window::~Win32Window() {
 			this->displayFuture.wait();
 			this->displayFuture.wait();
 		}
 		}
 	#endif
 	#endif
-	windowLock.lock();
+	lockWindow();
 		// Destroy the invisible cursor
 		// Destroy the invisible cursor
 		DestroyCursor(this->noCursor);
 		DestroyCursor(this->noCursor);
 		// Destroy the native window
 		// Destroy the native window
 		DestroyWindow(this->hwnd);
 		DestroyWindow(this->hwnd);
-	windowLock.unlock();
+	unlockWindow();
 }
 }
 
 
 // The lock argument must be true if not already within a lock and false if inside of a lock.
 // The lock argument must be true if not already within a lock and false if inside of a lock.
@@ -672,7 +678,7 @@ void Win32Window::redraw(HWND& hwnd, bool lock, bool swap) {
 
 
 	if (lock) {
 	if (lock) {
 		// Any other requests will have to wait.
 		// Any other requests will have to wait.
-		windowLock.lock();
+		lockWindow();
 		// Last chance to prefetch events before uploading the canvas.
 		// Last chance to prefetch events before uploading the canvas.
 		this->prefetchEvents_impl();
 		this->prefetchEvents_impl();
 	}
 	}
@@ -701,7 +707,7 @@ void Win32Window::redraw(HWND& hwnd, bool lock, bool swap) {
 			SetDIBitsToDevice(targetContext, 0, 0, paddedWidth, height, 0, 0, 0, height, dsr::image_dangerous_getData(this->canvas[displayIndex]), &bmi, DIB_RGB_COLORS);
 			SetDIBitsToDevice(targetContext, 0, 0, paddedWidth, height, 0, 0, 0, height, dsr::image_dangerous_getData(this->canvas[displayIndex]), &bmi, DIB_RGB_COLORS);
 		EndPaint(this->hwnd, &paintStruct);
 		EndPaint(this->hwnd, &paintStruct);
 		if (lock) {
 		if (lock) {
-			windowLock.unlock();
+			unlockWindow();
 		}
 		}
 	};
 	};
 	#ifdef DISABLE_MULTI_THREADING
 	#ifdef DISABLE_MULTI_THREADING

+ 48 - 42
Source/windowManagers/X11Window.cpp

@@ -10,17 +10,23 @@
 #include "../DFPSR/api/timeAPI.h"
 #include "../DFPSR/api/timeAPI.h"
 #include "../DFPSR/gui/BackendWindow.h"
 #include "../DFPSR/gui/BackendWindow.h"
 #include "../DFPSR/base/heap.h"
 #include "../DFPSR/base/heap.h"
-
-// According to this documentation, XInitThreads doesn't have to be used if a mutex is wrapped around all the calls to XLib.
-//   https://tronche.com/gui/x/xlib/display/XInitThreads.html
-#include <mutex>
-#include <future>
 #include <climits>
 #include <climits>
 
 
-static std::mutex windowLock;
+#include "../DFPSR/settings.h"
+
+#ifndef DISABLE_MULTI_THREADING
+	// According to this documentation, XInitThreads doesn't have to be used if a mutex is wrapped around all the calls to XLib.
+	//   https://tronche.com/gui/x/xlib/display/XInitThreads.html
+	#include <mutex>
+	#include <future>
 
 
-// Enable this macro to disable multi-threading
-//#define DISABLE_MULTI_THREADING
+	static std::mutex windowLock;
+	inline void lockWindow() { windowLock.lock(); }
+	inline void unlockWindow() { windowLock.unlock(); }
+#else
+	inline void lockWindow() {}
+	inline void unlockWindow() {}
+#endif
 
 
 static const int bufferCount = 2;
 static const int bufferCount = 2;
 
 
@@ -148,13 +154,13 @@ void X11Window::saveToClipboard(const dsr::ReadableString &text, double timeoutI
 }
 }
 
 
 void X11Window::setCursorPosition(int x, int y) {
 void X11Window::setCursorPosition(int x, int y) {
-	windowLock.lock();
+	lockWindow();
 		XWarpPointer(this->display, this->window, this->window, 0, 0, this->windowWidth, this->windowHeight, x, y);
 		XWarpPointer(this->display, this->window, this->window, 0, 0, this->windowWidth, this->windowHeight, x, y);
-	windowLock.unlock();
+	unlockWindow();
 }
 }
 
 
 void X11Window::applyCursorVisibility_locked() {
 void X11Window::applyCursorVisibility_locked() {
-	windowLock.lock();
+	lockWindow();
 		if (this->visibleCursor) {
 		if (this->visibleCursor) {
 			// Reset to parent cursor
 			// Reset to parent cursor
 			XUndefineCursor(this->display, this->window);
 			XUndefineCursor(this->display, this->window);
@@ -162,7 +168,7 @@ void X11Window::applyCursorVisibility_locked() {
 			// Let the window display an empty cursor
 			// Let the window display an empty cursor
 			XDefineCursor(this->display, this->window, this->noCursor);
 			XDefineCursor(this->display, this->window, this->noCursor);
 		}
 		}
-	windowLock.unlock();
+	unlockWindow();
 }
 }
 
 
 bool X11Window::setCursorVisibility(bool visible) {
 bool X11Window::setCursorVisibility(bool visible) {
@@ -175,13 +181,13 @@ bool X11Window::setCursorVisibility(bool visible) {
 }
 }
 
 
 void X11Window::updateTitle_locked() {
 void X11Window::updateTitle_locked() {
-	windowLock.lock();
-		XSetStandardProperties(this->display, this->window, dsr::FixedAscii<512>(this->title), "Icon", None, NULL, 0, NULL);
-	windowLock.unlock();
+	lockWindow();
+		XSetStandardProperties(this->display, this->window, dsr::FixedAscii<512>(this->title).getPointer(), "Icon", None, NULL, 0, NULL);
+	unlockWindow();
 }
 }
 
 
 dsr::PackOrderIndex X11Window::getColorFormat_locked() {
 dsr::PackOrderIndex X11Window::getColorFormat_locked() {
-	windowLock.lock();
+	lockWindow();
 		XVisualInfo visualRequest;
 		XVisualInfo visualRequest;
 		visualRequest.screen = 0;
 		visualRequest.screen = 0;
 		visualRequest.depth = 32;
 		visualRequest.depth = 32;
@@ -217,7 +223,7 @@ dsr::PackOrderIndex X11Window::getColorFormat_locked() {
 			}
 			}
 			XFree(formatList);
 			XFree(formatList);
 		}
 		}
-	windowLock.unlock();
+	unlockWindow();
 	return result;
 	return result;
 }
 }
 
 
@@ -242,24 +248,24 @@ void X11Window::setFullScreen(bool enabled) {
 		this->createWindowed_locked(this->title, 800, 600); // TODO: Remember the dimensions from last windowed mode
 		this->createWindowed_locked(this->title, 800, 600); // TODO: Remember the dimensions from last windowed mode
 	}
 	}
 	this->applyCursorVisibility_locked();
 	this->applyCursorVisibility_locked();
-	windowLock.lock();
+	lockWindow();
 		listContentInClipboard();
 		listContentInClipboard();
-	windowLock.unlock();
+	unlockWindow();
 }
 }
 
 
 void X11Window::removeOldWindow_locked() {
 void X11Window::removeOldWindow_locked() {
-	windowLock.lock();
+	lockWindow();
 		if (this->windowState != 0) {
 		if (this->windowState != 0) {
 			XFreeGC(this->display, this->graphicsContext);
 			XFreeGC(this->display, this->graphicsContext);
 			XDestroyWindow(this->display, this->window);
 			XDestroyWindow(this->display, this->window);
 			XUngrabPointer(this->display, CurrentTime);
 			XUngrabPointer(this->display, CurrentTime);
 		}
 		}
 		this->windowState = 0;
 		this->windowState = 0;
-	windowLock.unlock();
+	unlockWindow();
 }
 }
 
 
 void X11Window::prepareWindow_locked() {
 void X11Window::prepareWindow_locked() {
-	windowLock.lock();
+	lockWindow();
 		// Set input masks
 		// Set input masks
 		XSelectInput(this->display, this->window,
 		XSelectInput(this->display, this->window,
 		  ExposureMask | StructureNotifyMask |
 		  ExposureMask | StructureNotifyMask |
@@ -271,13 +277,13 @@ void X11Window::prepareWindow_locked() {
 		// Listen to the window close event.
 		// Listen to the window close event.
 		Atom WM_DELETE_WINDOW = XInternAtom(this->display, "WM_DELETE_WINDOW", False);
 		Atom WM_DELETE_WINDOW = XInternAtom(this->display, "WM_DELETE_WINDOW", False);
 		XSetWMProtocols(this->display, this->window, &WM_DELETE_WINDOW, 1);
 		XSetWMProtocols(this->display, this->window, &WM_DELETE_WINDOW, 1);
-	windowLock.unlock();
+	unlockWindow();
 	// Reallocate the canvas
 	// Reallocate the canvas
 	this->resizeCanvas(this->windowWidth, this->windowHeight);
 	this->resizeCanvas(this->windowWidth, this->windowHeight);
 }
 }
 
 
 void X11Window::createGCWindow_locked(const dsr::String& title, int width, int height) {
 void X11Window::createGCWindow_locked(const dsr::String& title, int width, int height) {
-	windowLock.lock();
+	lockWindow();
 		// Request to resize the canvas and interface according to the new window
 		// Request to resize the canvas and interface according to the new window
 		this->windowWidth = width;
 		this->windowWidth = width;
 		this->windowHeight = height;
 		this->windowHeight = height;
@@ -287,42 +293,42 @@ void X11Window::createGCWindow_locked(const dsr::String& title, int width, int h
 		unsigned long white = WhitePixel(this->display, screenIndex);
 		unsigned long white = WhitePixel(this->display, screenIndex);
 		// Create a new window
 		// Create a new window
 		this->window = XCreateSimpleWindow(this->display, DefaultRootWindow(this->display), 0, 0, width, height, 0, white, black);
 		this->window = XCreateSimpleWindow(this->display, DefaultRootWindow(this->display), 0, 0, width, height, 0, white, black);
-	windowLock.unlock();
+	unlockWindow();
 
 
 	this->updateTitle_locked();
 	this->updateTitle_locked();
 
 
-	windowLock.lock();
+	lockWindow();
 		// Create a new graphics context
 		// Create a new graphics context
 		this->graphicsContext = XCreateGC(this->display, this->window, 0, 0);
 		this->graphicsContext = XCreateGC(this->display, this->window, 0, 0);
 		XSetBackground(this->display, this->graphicsContext, black);
 		XSetBackground(this->display, this->graphicsContext, black);
 		XSetForeground(this->display, this->graphicsContext, white);
 		XSetForeground(this->display, this->graphicsContext, white);
 		XClearWindow(this->display, this->window);
 		XClearWindow(this->display, this->window);
-	windowLock.unlock();
+	unlockWindow();
 }
 }
 
 
 void X11Window::createWindowed_locked(const dsr::String& title, int width, int height) {
 void X11Window::createWindowed_locked(const dsr::String& title, int width, int height) {
 	// Create the window
 	// Create the window
 	this->createGCWindow_locked(title, width, height);
 	this->createGCWindow_locked(title, width, height);
-	windowLock.lock();
+	lockWindow();
 		// Display the window when done placing it
 		// Display the window when done placing it
 		XMapRaised(this->display, this->window);
 		XMapRaised(this->display, this->window);
 
 
 		this->windowState = 1;
 		this->windowState = 1;
 		this->firstFrame = true;
 		this->firstFrame = true;
-	windowLock.unlock();
+	unlockWindow();
 	this->prepareWindow_locked();
 	this->prepareWindow_locked();
 }
 }
 
 
 void X11Window::createFullscreen_locked() {
 void X11Window::createFullscreen_locked() {
-	windowLock.lock();
+	lockWindow();
 		// Get the screen resolution
 		// Get the screen resolution
 		Screen* screenInfo = DefaultScreenOfDisplay(this->display);
 		Screen* screenInfo = DefaultScreenOfDisplay(this->display);
-	windowLock.unlock();
+	unlockWindow();
 
 
 	// Create the window
 	// Create the window
 	this->createGCWindow_locked(U"", screenInfo->width, screenInfo->height);
 	this->createGCWindow_locked(U"", screenInfo->width, screenInfo->height);
 
 
-	windowLock.lock();
+	lockWindow();
 		// Override redirect
 		// Override redirect
 		unsigned long valuemask = CWOverrideRedirect;
 		unsigned long valuemask = CWOverrideRedirect;
 		XSetWindowAttributes setwinattr;
 		XSetWindowAttributes setwinattr;
@@ -353,7 +359,7 @@ void X11Window::createFullscreen_locked() {
 
 
 		this->windowState = 2;
 		this->windowState = 2;
 		this->firstFrame = true;
 		this->firstFrame = true;
-	windowLock.unlock();
+	unlockWindow();
 	this->prepareWindow_locked();
 	this->prepareWindow_locked();
 }
 }
 
 
@@ -365,9 +371,9 @@ X11Window::X11Window(const dsr::String& title, int width, int height) {
 		height = 300;
 		height = 300;
 	}
 	}
 
 
-	windowLock.lock();
+	lockWindow();
 		this->display = XOpenDisplay(nullptr);
 		this->display = XOpenDisplay(nullptr);
-	windowLock.unlock();
+	unlockWindow();
 	if (this->display == nullptr) {
 	if (this->display == nullptr) {
 		dsr::throwError(U"Error! Failed to open XLib display!\n");
 		dsr::throwError(U"Error! Failed to open XLib display!\n");
 		return;
 		return;
@@ -718,7 +724,7 @@ void X11Window::prefetchEvents() {
 				}
 				}
 			}
 			}
 		}
 		}
-		windowLock.unlock();
+		unlockWindow();
 	}
 	}
 }
 }
 
 
@@ -735,7 +741,7 @@ static int destroyXImage(XImage *image) {
 
 
 // Locked because it overrides
 // Locked because it overrides
 void X11Window::resizeCanvas(int width, int height) {
 void X11Window::resizeCanvas(int width, int height) {
-	windowLock.lock();
+	lockWindow();
 		if (this->display) {
 		if (this->display) {
 			unsigned int defaultDepth = DefaultDepth(this->display, XDefaultScreen(this->display));
 			unsigned int defaultDepth = DefaultDepth(this->display, XDefaultScreen(this->display));
 			// Get the old canvas
 			// Get the old canvas
@@ -767,7 +773,7 @@ void X11Window::resizeCanvas(int width, int height) {
 				image->f.destroy_image = destroyXImage;
 				image->f.destroy_image = destroyXImage;
 			}
 			}
 		}
 		}
-	windowLock.unlock();
+	unlockWindow();
 }
 }
 
 
 X11Window::~X11Window() {
 X11Window::~X11Window() {
@@ -777,7 +783,7 @@ X11Window::~X11Window() {
 			this->displayFuture.wait();
 			this->displayFuture.wait();
 		}
 		}
 	#endif
 	#endif
-	windowLock.lock();
+	lockWindow();
 		if (this->display) {
 		if (this->display) {
 			this->terminateClipboard();
 			this->terminateClipboard();
 			XFreeCursor(this->display, this->noCursor);
 			XFreeCursor(this->display, this->noCursor);
@@ -786,7 +792,7 @@ X11Window::~X11Window() {
 			XCloseDisplay(this->display);
 			XCloseDisplay(this->display);
 			this->display = nullptr;
 			this->display = nullptr;
 		}
 		}
-	windowLock.unlock();
+	unlockWindow();
 }
 }
 
 
 void X11Window::showCanvas() {
 void X11Window::showCanvas() {
@@ -801,14 +807,14 @@ void X11Window::showCanvas() {
 		this->showIndex = (this->showIndex + 1) % bufferCount;
 		this->showIndex = (this->showIndex + 1) % bufferCount;
 		this->prefetchEvents();
 		this->prefetchEvents();
 		int displayIndex = this->showIndex;
 		int displayIndex = this->showIndex;
-		windowLock.lock();
+		lockWindow();
 		std::function<void()> task = [this, displayIndex]() {
 		std::function<void()> task = [this, displayIndex]() {
 				// Clamp canvas dimensions to the target window
 				// Clamp canvas dimensions to the target window
 				int width = std::min(dsr::image_getWidth(this->canvas[displayIndex]), this->windowWidth);
 				int width = std::min(dsr::image_getWidth(this->canvas[displayIndex]), this->windowWidth);
 				int height = std::min(dsr::image_getHeight(this->canvas[displayIndex]), this->windowHeight);
 				int height = std::min(dsr::image_getHeight(this->canvas[displayIndex]), this->windowHeight);
 				// Display the result
 				// Display the result
 				XPutImage(this->display, this->window, this->graphicsContext, this->canvasX[displayIndex], 0, 0, 0, 0, width, height);
 				XPutImage(this->display, this->window, this->graphicsContext, this->canvasX[displayIndex], 0, 0, 0, 0, width, height);
-			windowLock.unlock();
+			unlockWindow();
 		};
 		};
 		#ifdef DISABLE_MULTI_THREADING
 		#ifdef DISABLE_MULTI_THREADING
 			// Perform instantly
 			// Perform instantly