aboutsummaryrefslogtreecommitdiff
path: root/source-common
diff options
context:
space:
mode:
Diffstat (limited to 'source-common')
-rw-r--r--source-common/Macros.hpp29
-rw-r--r--source-common/MacrosCodegen.hpp4
-rw-r--r--source-common/RapidJsonHelper.hpp110
-rw-r--r--source-common/ScopeGuard.hpp60
-rw-r--r--source-common/StbImplementations.c14
-rw-r--r--source-common/Utils.cpp87
-rw-r--r--source-common/Utils.hpp61
-rw-r--r--source-common/YCombinator.hpp14
8 files changed, 379 insertions, 0 deletions
diff --git a/source-common/Macros.hpp b/source-common/Macros.hpp
new file mode 100644
index 0000000..b5d05fa
--- /dev/null
+++ b/source-common/Macros.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#define STRINGIFY_IMPL(text) #text
+#define STRINGIFY(text) STRINGIFY_IMPL(text)
+
+#define CONCAT_IMPL(a, b) a##b
+#define CONCAT(a, b) CONCAT_IMPL(a, b)
+#define CONCAT_3(a, b, c) CONCAT(a, CONCAT(b, c))
+#define CONCAT_4(a, b, c, d) CONCAT(CONCAT(a, b), CONCAT(c, d))
+
+#define UNIQUE_NAME(prefix) CONCAT(prefix, __COUNTER__)
+#define UNIQUE_NAME_LINE(prefix) CONCAT(prefix, __LINE__)
+#define DISCARD UNIQUE_NAME(_discard)
+
+#define UNUSED(x) (void)x;
+
+#if defined(_MSC_VER)
+# define UNREACHABLE __assume(0)
+#elif defined(__GNUC__) || defined(__clang__)
+# define UNREACHABLE __builtin_unreachable()
+#else
+# define UNREACHABLE
+#endif
+
+#if _WIN32
+# define PLATFORM_PATH_STR "%ls"
+#else
+# define PLATFORM_PATH_STR "%s"
+#endif
diff --git a/source-common/MacrosCodegen.hpp b/source-common/MacrosCodegen.hpp
new file mode 100644
index 0000000..6c93d09
--- /dev/null
+++ b/source-common/MacrosCodegen.hpp
@@ -0,0 +1,4 @@
+#pragma once
+
+#define BRUSSEL_METACLASS
+#define BRUSSEL_METAENUM(name)
diff --git a/source-common/RapidJsonHelper.hpp b/source-common/RapidJsonHelper.hpp
new file mode 100644
index 0000000..75cd93a
--- /dev/null
+++ b/source-common/RapidJsonHelper.hpp
@@ -0,0 +1,110 @@
+#pragma once
+
+#include <rapidjson/document.h>
+#include <cstring>
+#include <string>
+#include <string_view>
+
+#define BRUSSEL_JSON_GET(object, name, type, out, failAction) \
+ { \
+ auto it = (object).FindMember(name); \
+ if (it == (object).MemberEnd()) failAction; \
+ auto& value = it->value; \
+ if (!value.Is<type>()) failAction; \
+ (out) = value.Get<type>(); \
+ }
+
+#define BRUSSEL_JSON_GET_DEFAULT(object, name, type, out, theDefault) \
+ do { \
+ auto it = (object).FindMember(name); \
+ if (it == (object).MemberEnd()) { \
+ (out) = theDefault; \
+ break; \
+ } \
+ auto& value = it->value; \
+ if (!value.Is<type>()) { \
+ (out) = theDefault; \
+ break; \
+ } \
+ (out) = value.Get<type>(); \
+ } while (0);
+
+namespace rapidjson {
+
+inline const Value* GetProperty(const Value& value, std::string_view name) {
+ for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) {
+ if (it->name.GetStringLength() != name.size()) continue;
+ if (std::memcmp(it->name.GetString(), name.data(), name.size())) continue;
+
+ return &it->value;
+ }
+ return nullptr;
+}
+
+inline const Value* GetProperty(const Value& value, Type type, std::string_view name) {
+ for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) {
+ if (it->name.GetStringLength() != name.size()) continue;
+ if (it->value.GetType() != type) continue;
+ if (std::memcmp(it->name.GetString(), name.data(), name.size())) continue;
+
+ return &it->value;
+ }
+ return nullptr;
+}
+
+inline std::string_view AsStringView(const Value& value) {
+ return std::string_view(value.GetString(), value.GetStringLength());
+}
+
+inline std::string_view AsStringView(const GenericStringRef<char>& strRef) {
+ return std::string_view(strRef.s, strRef.length);
+}
+
+inline std::string AsString(const Value& value) {
+ return std::string(value.GetString(), value.GetStringLength());
+}
+
+inline std::string AsString(const GenericStringRef<char>& strRef) {
+ return std::string(strRef.s, strRef.length);
+}
+
+// RapidJson itself already provides std::string and const char* overloads
+inline GenericStringRef<char> StringRef(std::string_view str) {
+ return GenericStringRef<char>(
+ str.data() ? str.data() : "",
+ str.size());
+}
+
+template <class TIter, class TSentienl>
+rapidjson::Value WriteVectorPrimitives(rapidjson::Document& root, TIter begin, TSentienl end) {
+ using TElement = typename TIter::value_type;
+
+ rapidjson::Value list;
+ while (begin != end) {
+ if constexpr (std::is_same_v<TElement, std::string>) {
+ auto& elm = *begin;
+ list.PushBack(rapidjson::Value(elm.c_str(), elm.size()), root.GetAllocator());
+ } else {
+ list.PushBack(*begin, root.GetAllocator());
+ }
+ ++begin;
+ }
+ return list;
+}
+
+template <class TContainer>
+bool ReadVectorPrimitives(const rapidjson::Value& value, TContainer& list) {
+ using TElement = typename TContainer::value_type;
+
+ if (!value.IsArray()) return false;
+
+ list.reserve(value.Size());
+ for (auto& elm : value.GetArray()) {
+ if (!elm.Is<TElement>()) return {};
+ list.push_back(elm.Get<TElement>());
+ }
+
+ return true;
+}
+
+} // namespace rapidjson
diff --git a/source-common/ScopeGuard.hpp b/source-common/ScopeGuard.hpp
new file mode 100644
index 0000000..28f3385
--- /dev/null
+++ b/source-common/ScopeGuard.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "Macros.hpp"
+
+#include <utility>
+
+template <class TCleanupFunc>
+class ScopeGuard {
+private:
+ TCleanupFunc mFunc;
+ bool mDismissed = false;
+
+public:
+ /// Specifically left this implicit so that constructs like
+ /// \code
+ /// ScopeGuard sg = [&]() { res.Cleanup(); };
+ /// \endcode
+ /// would work. It is highly discourage and unlikely that one would want to use ScopeGuard as a function
+ /// parameter, so the normal argument that implicit conversion are harmful doesn't really apply here.
+ // Deliberately not explicit to allow usages like: ScopeGuard var = lambda;
+ ScopeGuard(TCleanupFunc&& function) noexcept
+ : mFunc{ std::move(function) } {
+ }
+
+ ~ScopeGuard() noexcept {
+ if (!mDismissed) {
+ mFunc();
+ }
+ }
+
+ ScopeGuard(const ScopeGuard&) = delete;
+ ScopeGuard& operator=(const ScopeGuard&) = delete;
+
+ ScopeGuard(ScopeGuard&& that) noexcept
+ : mFunc{ std::move(that.mFunc) } {
+ that.Cancel();
+ }
+
+ ScopeGuard& operator=(ScopeGuard&& that) noexcept {
+ if (!mDismissed) {
+ mFunc();
+ }
+ this->mFunc = std::move(that.mFunc);
+ this->cancelled = std::exchange(that.cancelled, true);
+ }
+
+ void Dismiss() noexcept {
+ mDismissed = true;
+ }
+};
+
+template <class T>
+auto GuardDeletion(T* ptr) {
+ return ScopeGuard([ptr]() {
+ delete ptr;
+ });
+}
+
+#define SCOPE_GUARD(name) ScopeGuard name = [&]()
+#define DEFER ScopeGuard UNIQUE_NAME(scopeGuard) = [&]()
diff --git a/source-common/StbImplementations.c b/source-common/StbImplementations.c
new file mode 100644
index 0000000..73bbc2a
--- /dev/null
+++ b/source-common/StbImplementations.c
@@ -0,0 +1,14 @@
+#define STB_RECT_PACK_IMPLEMENTATION
+#include <stb_rect_pack.h>
+
+#define STB_TRUETYPE_IMPLEMENTATION
+#include <stb_truetype.h>
+
+#define STB_IMAGE_IMPLEMENTATION
+#include <stb_image.h>
+
+#define STB_SPRINTF_IMPLEMENTATION
+#include <stb_sprintf.h>
+
+#define STB_C_LEXER_IMPLEMENTATION
+#include <stb_c_lexer.h>
diff --git a/source-common/Utils.cpp b/source-common/Utils.cpp
new file mode 100644
index 0000000..53b3863
--- /dev/null
+++ b/source-common/Utils.cpp
@@ -0,0 +1,87 @@
+#include "Utils.hpp"
+
+#ifdef _WIN32
+# include <Windows.h>
+#endif
+
+#ifdef _WIN32
+# define BRUSSEL_MODE_STRING(string) L##string
+#else
+# define BRUSSEL_MODE_STRING(string) string
+#endif
+
+#if _WIN32
+using FopenModeString = const wchar_t*;
+#else
+using FopenModeString = const char*;
+#endif
+
+static FopenModeString GetModeString(Utils::IoMode mode, bool binary) {
+ using namespace Utils;
+ if (binary) {
+ switch (mode) {
+ case Read: return BRUSSEL_MODE_STRING("rb");
+ case WriteTruncate: return BRUSSEL_MODE_STRING("wb");
+ case WriteAppend: return BRUSSEL_MODE_STRING("ab");
+ }
+ } else {
+ switch (mode) {
+ case Read: return BRUSSEL_MODE_STRING("r");
+ case WriteTruncate: return BRUSSEL_MODE_STRING("w");
+ case WriteAppend: return BRUSSEL_MODE_STRING("a");
+ }
+ }
+ return nullptr;
+}
+
+FILE* Utils::OpenCstdioFile(const std::filesystem::path& path, IoMode mode, bool binary) {
+#ifdef _WIN32
+ // std::filesystem::path::c_str() returns `const wchar_t*` under Windows, because NT uses UTF-16 natively
+ // NOTE: _wfopen() only affects the type of path parameter, otherwise the file stream created is identical to the one by fopen()
+ return _wfopen(path.c_str(), ::GetModeString(mode, binary));
+#else
+ return fopen(path.c_str(), ::GetModeString(mode, binary));
+#endif
+}
+
+FILE* Utils::OpenCstdioFile(const char* path, IoMode mode, bool binary) {
+#ifdef _WIN32
+ // On Windows, fopen() accepts ANSI codepage encoded path, convert our UTF-8 string to UTF-16 to ensure that no matter what the locale is, the path continues to work
+ WCHAR platformPath[MAX_PATH];
+ if (MultiByteToWideChar(CP_UTF8, 0, path, -1, platformPath, MAX_PATH) == 0) {
+ return nullptr;
+ }
+ return _wfopen(platformPath, ::GetModeString(mode, binary));
+#else
+ return fopen(path, ::GetModeString(mode, binary));
+#endif
+}
+
+bool Utils::InRangeInclusive(int n, int lower, int upper) {
+ if (lower > upper) {
+ std::swap(lower, upper);
+ }
+ return n >= lower && n <= upper;
+}
+
+bool Utils::LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate) {
+ bool verticalLine = p1.x == p2.x && InRangeInclusive(candidate.x, p1.x, p2.x);
+ bool horizontalLine = p1.y == p2.y && InRangeInclusive(candidate.y, p1.y, p2.y);
+ return verticalLine && horizontalLine;
+}
+
+bool Utils::IsColinear(glm::ivec2 p1, glm::ivec2 p2) {
+ return p1.x == p2.x || p1.y == p2.y;
+}
+
+std::string Utils::MakeRandomNumberedName(const char* tag) {
+ int n = std::rand();
+#define RNG_NAME_PATTERN "Unnamed %s #%d", tag, n
+ // NOTE: does not include null-terminator
+ int size = snprintf(nullptr, 0, RNG_NAME_PATTERN);
+ std::string result;
+ result.resize(size); // std::string::resize handles storage for null-terminator alreaedy
+ snprintf(result.data(), size, RNG_NAME_PATTERN);
+#undef RNG_NAME_PATTERN
+ return result;
+}
diff --git a/source-common/Utils.hpp b/source-common/Utils.hpp
new file mode 100644
index 0000000..9f28aad
--- /dev/null
+++ b/source-common/Utils.hpp
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <robin_hood.h>
+#include <cstdio>
+#include <cstring>
+#include <filesystem>
+#include <glm/glm.hpp>
+
+namespace Utils {
+
+enum IoMode {
+ Read,
+ WriteTruncate,
+ WriteAppend,
+};
+
+FILE* OpenCstdioFile(const std::filesystem::path& path, IoMode mode, bool binary = false);
+FILE* OpenCstdioFile(const char* path, IoMode mode, bool binary = false);
+
+constexpr float Abs(float v) noexcept {
+ return v < 0.0f ? -v : v;
+}
+
+bool InRangeInclusive(int n, int lower, int upper);
+bool LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate);
+
+bool IsColinear(glm::ivec2 p1, glm::ivec2 p2);
+
+template <class T>
+void HashCombine(std::size_t& seed, const T& v) {
+ seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+}
+
+std::string MakeRandomNumberedName(const char* tag);
+
+} // namespace Utils
+
+struct StringHash {
+ using is_transparent = void;
+
+ std::size_t operator()(const std::string& key) const { return robin_hood::hash_bytes(key.c_str(), key.size()); }
+ std::size_t operator()(std::string_view key) const { return robin_hood::hash_bytes(key.data(), key.size()); }
+ std::size_t operator()(const char* key) const { return robin_hood::hash_bytes(key, std::strlen(key)); }
+};
+
+struct StringEqual {
+ using is_transparent = int;
+
+ bool operator()(std::string_view lhs, const std::string& rhs) const {
+ const std::string_view view = rhs;
+ return lhs == view;
+ }
+
+ bool operator()(const char* lhs, const std::string& rhs) const {
+ return std::strcmp(lhs, rhs.c_str()) == 0;
+ }
+
+ bool operator()(const std::string& lhs, const std::string& rhs) const {
+ return lhs == rhs;
+ }
+};
diff --git a/source-common/YCombinator.hpp b/source-common/YCombinator.hpp
new file mode 100644
index 0000000..b1d2350
--- /dev/null
+++ b/source-common/YCombinator.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+template <class Func>
+struct YCombinator {
+ // NOTE: implicit constructor allows initializing this
+ Func func;
+
+ template <class... Ts>
+ decltype(auto) operator()(Ts&&... args) const {
+ // NOTE: static_cast<Ts>(args)... is equivalent to std::forward<Ts>(args)...
+ // written this way so that we don't have to include <utility>, as well as reducing template instanciations to help compile time
+ return func(*this, static_cast<Ts>(args)...);
+ }
+};