├── .editorconfig ├── test ├── premake.bat ├── premake5.exe ├── .gitignore ├── Main.cpp └── premake.lua ├── Bedrock ├── Debug.h ├── Core.cpp ├── Array.cpp ├── Random.h ├── Hash.cpp ├── FunctionRef.cpp ├── TempMemory.h ├── Semaphore.h ├── TempMemory.cpp ├── Ticks.h ├── ConditionVariable.h ├── Move.h ├── PlacementNew.h ├── Event.h ├── Debug.cpp ├── Span.cpp ├── Storage.cpp ├── UniquePtr.cpp ├── Bedrock.natstepfilter ├── Trace.h ├── Memory.h ├── Algorithm.cpp ├── Trace.cpp ├── Assert.cpp ├── Mutex.cpp ├── Time.h ├── Mutex.h ├── Function.cpp ├── MemoryArena.cpp ├── Array.h ├── InitializerList.h ├── Storage.h ├── Semaphore.cpp ├── Ticks.cpp ├── Assert.h ├── Compare.cpp ├── FunctionRef.h ├── UniquePtr.h ├── Event.cpp ├── Thread.h ├── Bedrock.natvis ├── Hash.h ├── Memory.cpp ├── ConditionVariable.cpp ├── StringView.cpp ├── StringFormat.cpp ├── Test.cpp ├── Function.h ├── Atomic.cpp ├── Thread.cpp ├── StringFormat.h ├── Test.h ├── Span.h ├── String.cpp ├── TypeTraits.h ├── Algorithm.h ├── Compare.h ├── Allocator.h ├── HashMap.cpp ├── StringView.h ├── Core.h ├── Atomic.h ├── String.h ├── Vector.cpp └── MemoryArena.h ├── .gitmodules ├── .clang-format ├── .github └── workflows │ └── msbuild.yml ├── README.md └── LICENSE /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_style = tab 4 | indent_size = 4 -------------------------------------------------------------------------------- /test/premake.bat: -------------------------------------------------------------------------------- 1 | premake5 --file=premake.lua vs2022 2 | if %errorlevel% NEQ 0 PAUSE -------------------------------------------------------------------------------- /test/premake5.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jlaumon/Bedrock/HEAD/test/premake5.exe -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | obj/ 3 | bin/ 4 | enc_temp_folder/ 5 | *.sln 6 | *.vcxproj 7 | *.vcxproj.filters 8 | *.user 9 | -------------------------------------------------------------------------------- /Bedrock/Debug.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | // Check if a debugger is attached. 5 | bool gIsDebuggerAttached(); 6 | 7 | // Set the name of the current thread. 8 | void gSetCurrentThreadName(const char* inName); -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Bedrock/thirdparty/rapidhash"] 2 | path = Bedrock/thirdparty/rapidhash 3 | url = https://github.com/jlaumon/rapidhash 4 | [submodule "Bedrock/thirdparty/stb"] 5 | path = Bedrock/thirdparty/stb 6 | url = https://github.com/nothings/stb.git 7 | -------------------------------------------------------------------------------- /Bedrock/Core.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | 4 | 5 | // Tests for the constexpr code in gCountLeadingZeros64. 6 | static_assert(gCountLeadingZeros64(0) == 64); 7 | static_assert(gCountLeadingZeros64(1) == 63); 8 | static_assert(gCountLeadingZeros64(cMaxUInt64) == 0); 9 | static_assert(gCountLeadingZeros64(cMaxUInt32) == 32); 10 | 11 | -------------------------------------------------------------------------------- /test/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char* argv[]) 5 | { 6 | // Initialize some temp memory - Trace needs it. 7 | gThreadInitTempMemory(gMemAlloc(1_MiB)); 8 | defer { gMemFree(gThreadExitTempMemory()); }; 9 | 10 | // Run the tests. 11 | TestResult result = gRunTests(); 12 | 13 | return (result == TestResult::Success) ? 0 : 1; 14 | } 15 | -------------------------------------------------------------------------------- /Bedrock/Array.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | 6 | REGISTER_TEST("Array") 7 | { 8 | Array values = { 1, 2, 3, 4, 5 }; 9 | Span test = values; 10 | 11 | // Mostly checking that things compile. 12 | TEST_TRUE(test == values); 13 | 14 | values.Fill(8); 15 | for (int value : values) 16 | TEST_TRUE(value == 8); 17 | }; 18 | -------------------------------------------------------------------------------- /Bedrock/Random.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | // Simple random function. 8 | inline uint32 gRand32(uint32 inSeed = 0) 9 | { 10 | if (inSeed == 0) 11 | inSeed = (uint32)gGetTickCount(); 12 | 13 | // Equivalent to std::minstd_rand 14 | constexpr uint32 cMul = 48271; 15 | constexpr uint32 cMod = 2147483647; 16 | return inSeed * cMul % cMod; 17 | } -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Microsoft 3 | AccessModifierOffset: '-4' 4 | AlignConsecutiveAssignments: Consecutive 5 | AlignConsecutiveDeclarations: Consecutive 6 | AlignConsecutiveMacros: Consecutive 7 | AlignConsecutiveBitFields: Consecutive 8 | AlignEscapedNewlines: Left 9 | AlignTrailingComments: 'true' 10 | Cpp11BracedListStyle: 'false' 11 | PointerAlignment: Left 12 | TabWidth: '4' 13 | UseTab: Always 14 | AllowShortFunctionsOnASingleLine: Inline 15 | ColumnLimit: 128 16 | ... 17 | -------------------------------------------------------------------------------- /Bedrock/Hash.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | inline uint64 gHash(StringView inValue, uint64 inSeed = cHashSeed) 8 | { 9 | return gHash(inValue.Begin(), inValue.Size(), inSeed); 10 | } 11 | 12 | 13 | REGISTER_TEST("Hash") 14 | { 15 | uint64 hash = gHash(42); 16 | TEST_TRUE(hash != 0); 17 | 18 | uint64 hash2 = gHash(StringView("hello what's up"), hash); 19 | TEST_TRUE(hash2 != hash); 20 | }; 21 | -------------------------------------------------------------------------------- /Bedrock/FunctionRef.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | 5 | 6 | 7 | REGISTER_TEST("FunctionRef") 8 | { 9 | int i = 1; 10 | int j = 3; 11 | 12 | auto lambda = [i, j](int k) { return i + j + k; }; 13 | FunctionRef func = lambda; 14 | TEST_TRUE(func(4) == 8); 15 | TEST_TRUE(func.IsValid()); 16 | 17 | func = {}; 18 | TEST_TRUE(!func.IsValid()); 19 | 20 | int (*func_ptr)(int) = [](int k) { return k * k; }; 21 | func = func_ptr; 22 | TEST_TRUE(func(4) == 16); 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /Bedrock/TempMemory.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | 8 | // Initialize temporary memory for the current thread. 9 | void gThreadInitTempMemory(MemBlock inMemory); 10 | 11 | // De-initialize temporary memory for the current thread. 12 | [[nodiscard]] MemBlock gThreadExitTempMemory(); 13 | 14 | // Thread-local arena that can be used for allocating temporary memory. 15 | using TempMemArena = MemArena<>; 16 | inline thread_local TempMemArena gTempMemArena; 17 | 18 | 19 | -------------------------------------------------------------------------------- /Bedrock/Semaphore.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | 8 | using OSSemaphore = void*; 9 | 10 | 11 | struct Semaphore : NoCopy 12 | { 13 | Semaphore(int inInitialCount, int inMaxCount); 14 | ~Semaphore(); 15 | 16 | bool TryAcquire(); 17 | bool TryAcquireFor(NanoSeconds inTimeout); 18 | void Acquire(); 19 | void Release(int inCount = 1); 20 | 21 | OSSemaphore GetOSHandle() const { return mOSSemaphore; } 22 | 23 | private: 24 | 25 | OSSemaphore mOSSemaphore = {}; 26 | }; 27 | -------------------------------------------------------------------------------- /Bedrock/TempMemory.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | 4 | 5 | void gThreadInitTempMemory(MemBlock inMemory) 6 | { 7 | gAssert(gTempMemArena.GetMemBlock() == nullptr); // Already initialized. 8 | 9 | gTempMemArena = { inMemory }; 10 | } 11 | 12 | 13 | MemBlock gThreadExitTempMemory() 14 | { 15 | // Everything should be freed before exiting (or not initialized). 16 | gAssert(gTempMemArena.GetAllocatedSize() == 0); 17 | 18 | MemBlock mem_block = gTempMemArena.GetMemBlock(); 19 | gTempMemArena = {}; 20 | return mem_block; 21 | } 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Bedrock/Ticks.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | 6 | int64 gGetTickCount(); 7 | int64 gTicksToNanoseconds(int64 inTicks); 8 | double gTicksToMilliseconds(int64 inTicks); 9 | double gTicksToSeconds(int64 inTicks); 10 | int64 gNanosecondsToTicks(int64 inNanoseconds); 11 | int64 gMillisecondsToTicks(double inMilliseconds); 12 | int64 gSecondsToTicks(double inSeconds); 13 | 14 | 15 | const int64 gProcessStartTicks = gGetTickCount(); 16 | 17 | struct Timer 18 | { 19 | Timer() { Reset(); } 20 | void Reset() { mTicks = gGetTickCount(); } 21 | int64 GetTicks() const { return gGetTickCount() - mTicks; } 22 | int64 mTicks = 0; 23 | }; -------------------------------------------------------------------------------- /Bedrock/ConditionVariable.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | using OSCondVar = void*; 10 | 11 | 12 | 13 | struct ConditionVariable : NoCopy 14 | { 15 | static constexpr NanoSeconds cInfiniteTimeout = (NanoSeconds)-1; 16 | 17 | enum class WaitResult 18 | { 19 | Success, 20 | Timeout 21 | }; 22 | 23 | 24 | ConditionVariable(); 25 | ~ConditionVariable(); 26 | 27 | void NotifyOne(); 28 | void NotifyAll(); 29 | 30 | WaitResult Wait(MutexLockGuard& ioLock, NanoSeconds inTimeout = cInfiniteTimeout); 31 | 32 | private: 33 | OSCondVar mOSCondVar = nullptr; 34 | }; 35 | -------------------------------------------------------------------------------- /Bedrock/Move.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | 8 | // Equivalent to std::move 9 | template 10 | [[nodiscard]] ATTRIBUTE_INTRINSIC constexpr RemoveReference&& gMove(taType&& ioArg) { return static_cast&&>(ioArg); } 11 | 12 | 13 | /// Equivalent to std::forward 14 | template 15 | [[nodiscard]] ATTRIBUTE_INTRINSIC constexpr taType&& gForward(RemoveReference& ioArg) { return static_cast(ioArg); } 16 | template 17 | [[nodiscard]] ATTRIBUTE_INTRINSIC constexpr taType&& gForward(RemoveReference&& ioArg) 18 | { 19 | static_assert(!cIsLValueReference, "Can't forward an lvalue reference."); 20 | return static_cast(ioArg); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /Bedrock/PlacementNew.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace Details { struct PlacementNewTag {}; } 8 | constexpr void* operator new(size_t, void* inPtr, Details::PlacementNewTag) noexcept { return inPtr; } 9 | constexpr void operator delete(void*, void*, Details::PlacementNewTag) noexcept {} 10 | 11 | // Placement new 12 | template 13 | inline void gPlacementNew(taType& ioStorage, taArgs&&... inArgs) { new (&ioStorage, Details::PlacementNewTag{}) taType(gForward(inArgs)...); } 14 | 15 | // Placement new that calls the default constructor only if there's one (no zero-initialization). 16 | template 17 | inline void gPlacementNewNoZeroInit(taType& ioStorage) { new (&ioStorage, Details::PlacementNewTag{}) taType; } 18 | -------------------------------------------------------------------------------- /Bedrock/Event.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | 8 | using OSEvent = void*; 9 | 10 | 11 | struct Event : NoCopy 12 | { 13 | enum ResetMode 14 | { 15 | AutoReset, // The event is reset automatically after waiting on it. 16 | ManualReset // The event stays set until Reset is called. 17 | }; 18 | 19 | Event(ResetMode inResetMode, bool inInitialState = false); 20 | ~Event(); 21 | 22 | void Set(); // Set the event. 23 | void Reset(); // Reset the event. 24 | 25 | bool TryWait() const; // Return true if the event is set. 26 | bool TryWaitFor(NanoSeconds inTimeout) const; // Wait until the event is set (return true), or until timeout (return false). 27 | void Wait() const; // Wait until the event is set. 28 | 29 | OSEvent GetOSHandle() const { return mOSEvent; } 30 | private: 31 | 32 | OSEvent mOSEvent = {}; 33 | }; 34 | -------------------------------------------------------------------------------- /Bedrock/Debug.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | 5 | #define VC_EXTRALEAN 6 | #define WIN32_LEAN_AND_MEAN 7 | #define NOMINMAX 8 | #include 9 | 10 | 11 | // Check if a debugger is attached. 12 | bool gIsDebuggerAttached() 13 | { 14 | return IsDebuggerPresent(); 15 | } 16 | 17 | 18 | // Set the name of the current thread. 19 | void gSetCurrentThreadName(const char* inName) 20 | { 21 | gAssert(inName[0] != 0); // Don't set an empty name. 22 | 23 | wchar_t wchar_name[256]; 24 | 25 | // Convert the name to wide chars. 26 | int written_wchars = MultiByteToWideChar(CP_UTF8, 0, inName, gStrLen(inName), wchar_name, (int)(gElemCount(wchar_name) - 1)); 27 | 28 | if (written_wchars == 0) 29 | return; // Conversion failed. 30 | 31 | // Make sure the string is null-terminated. 32 | wchar_name[written_wchars] = 0; 33 | 34 | SetThreadDescription(GetCurrentThread(), wchar_name); 35 | } -------------------------------------------------------------------------------- /Bedrock/Span.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | 5 | REGISTER_TEST("Span") 6 | { 7 | int values[] = { 1, 2, 3, 4, 5 }; 8 | Span test = values; 9 | 10 | Span first_two = test.First(2); 11 | TEST_TRUE(first_two.Size() == 2); 12 | TEST_TRUE(first_two[0] == 1); 13 | TEST_TRUE(first_two[1] == 2); 14 | 15 | Span last_two = test.Last(2); 16 | TEST_TRUE(last_two.Size() == 2); 17 | TEST_TRUE(last_two[0] == 4); 18 | TEST_TRUE(last_two[1] == 5); 19 | 20 | TEST_TRUE(test.SubSpan(0) == test); 21 | TEST_TRUE(test.SubSpan(4, 10).Size() == 1); 22 | TEST_TRUE(test.SubSpan(4, -1).Size() == 1); // Negative count behaves like cMaxInt 23 | TEST_TRUE(test.SubSpan(4, -5).Size() == 1); 24 | 25 | // Conversions to const, mostly to check that it compiles. 26 | Span const_test = values; 27 | const_test = test; 28 | TEST_TRUE(test == const_test); 29 | TEST_TRUE(const_test == values); 30 | }; 31 | -------------------------------------------------------------------------------- /Bedrock/Storage.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | 5 | 6 | REGISTER_TEST("Storage") 7 | { 8 | struct TestObject 9 | { 10 | TestObject(int& inCounter) 11 | : mCounter(inCounter) 12 | { 13 | mCounter++; 14 | } 15 | 16 | ~TestObject() 17 | { 18 | mCounter--; 19 | } 20 | 21 | int& mCounter; 22 | }; 23 | 24 | int counter = 0; 25 | 26 | { 27 | Storage object; 28 | 29 | TEST_TRUE(object.IsCreated() == false); 30 | TEST_TRUE(counter == 0); 31 | 32 | object.Create(counter); 33 | 34 | TEST_TRUE(object.IsCreated()); 35 | TEST_TRUE(counter == 1); 36 | TEST_TRUE(object->mCounter == counter); 37 | TEST_TRUE((TestObject*)object != nullptr); // Mostly to check it compiles 38 | 39 | object.Destroy(); 40 | 41 | TEST_TRUE(counter == 0); 42 | 43 | object.Create(counter); 44 | 45 | TEST_TRUE(counter == 1); 46 | } 47 | 48 | TEST_TRUE(counter == 0); 49 | }; -------------------------------------------------------------------------------- /Bedrock/UniquePtr.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | REGISTER_TEST("UniquePtr") 8 | { 9 | struct TestObject 10 | { 11 | TestObject(int& inCounter) 12 | : mCounter(inCounter) 13 | { 14 | mCounter++; 15 | } 16 | 17 | ~TestObject() 18 | { 19 | mCounter--; 20 | } 21 | 22 | int& mCounter; 23 | }; 24 | 25 | int counter = 0; 26 | 27 | { 28 | UniquePtr ptr(new TestObject(counter)); 29 | TEST_TRUE(counter == 1); 30 | } 31 | TEST_TRUE(counter == 0); 32 | 33 | { 34 | UniquePtr ptr; 35 | 36 | TEST_TRUE(ptr == nullptr); 37 | TEST_TRUE(!ptr); 38 | TEST_TRUE(counter == 0); 39 | 40 | ptr = UniquePtr(new TestObject(counter)); 41 | TEST_TRUE(counter == 1); 42 | 43 | UniquePtr ptr2 = gMove(ptr); 44 | TEST_TRUE(counter == 1); 45 | TEST_TRUE(ptr == nullptr); 46 | 47 | ptr2 = nullptr; 48 | TEST_TRUE(counter == 0); 49 | } 50 | }; -------------------------------------------------------------------------------- /Bedrock/Bedrock.natstepfilter: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | gMove<.* 5 | NoStepInto 6 | 7 | 8 | gForward<.* 9 | NoStepInto 10 | 11 | 12 | gPlacementNew<.* 13 | NoStepInto 14 | 15 | 16 | gPlacementNewNoZeroInit<.* 17 | NoStepInto 18 | 19 | 20 | Storage<.*::operator-> 21 | NoStepInto 22 | 23 | 24 | UniquePtr<.*::operator-> 25 | NoStepInto 26 | 27 | 28 | StringView::StringView(.* 29 | NoStepInto 30 | 31 | 32 | Span<.*::Span.* 33 | NoStepInto 34 | 35 | -------------------------------------------------------------------------------- /Bedrock/Trace.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | 6 | // Trace/print a formatted string. 7 | // By default outputs to stdout, but that can be customized with gSetTraceCallback. 8 | // Note: The fake call to printf is there to catch format errors. Unlike attribute format, this also works with MSVC. 9 | #define gTrace(...) \ 10 | do \ 11 | { \ 12 | (void)sizeof(printf(__VA_ARGS__)); \ 13 | Details::Trace(__VA_ARGS__); \ 14 | } while (false) 15 | 16 | 17 | namespace Details 18 | { 19 | // Internal function doing the actual tracing. 20 | void Trace(const char* inFormat, ...); 21 | } 22 | 23 | 24 | struct StringView; 25 | using TraceCallback = void(*)(StringView inFormattedStr); 26 | 27 | // Set a callback for every trace. Can be used to do custom logging. 28 | void gSetTraceCallback(TraceCallback inCallback); 29 | 30 | // Printf forward declaration for the format validation hack above. 31 | extern "C" int __cdecl printf(const char* inFormat, ...); 32 | 33 | 34 | -------------------------------------------------------------------------------- /Bedrock/Memory.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | 6 | struct MemBlock 7 | { 8 | uint8* mPtr = nullptr; 9 | int64 mSize = 0; 10 | 11 | constexpr bool operator==(NullPtrType) const { return mPtr == nullptr; } 12 | }; 13 | 14 | 15 | // Heap 16 | 17 | MemBlock gMemAlloc(int64 inSize); // Allocate heap memory. 18 | void gMemFree(MemBlock inMemory); // Free heap memory. 19 | 20 | 21 | // Virtual Memory 22 | 23 | int gVMemReserveGranularity(); // Return the granularity at which memory can be reserved. 24 | int gVMemCommitGranularity(); // Return the granularity at which memory can be committed. 25 | MemBlock gVMemReserve(int64 inSize); // Reserve some memory. inSize will be rounded up to reserve granularity. 26 | void gVMemFree(MemBlock inMemory); // Free previously reserved memory. 27 | MemBlock gVMemCommit(MemBlock inMemory); // Commit some reserved memory. 28 | // On success, return the committed MemBlock (inMemory rounded up/down to commit granularity). 29 | // On failure, return a nullptr MemBlock. 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Bedrock/Algorithm.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | 6 | REGISTER_TEST("ReverseIterator") 7 | { 8 | StringView test = "test"; 9 | 10 | ReverseIterator reverse_begin{ test.End() }; 11 | ReverseIterator reverse_end{ test.Begin() }; 12 | TEST_TRUE(reverse_begin != reverse_end); 13 | 14 | ReverseIterator reverse_iter = reverse_begin; 15 | TEST_TRUE(*reverse_iter == 't'); ++reverse_iter; 16 | TEST_TRUE(*reverse_iter == 's'); ++reverse_iter; 17 | TEST_TRUE(*reverse_iter == 'e'); ++reverse_iter; 18 | TEST_TRUE(*reverse_iter == 't'); ++reverse_iter; 19 | TEST_TRUE(reverse_iter == reverse_end); 20 | }; 21 | 22 | 23 | REGISTER_TEST("AnyOf") 24 | { 25 | int values[] = { 1, 2, 3, 4, 5 }; 26 | 27 | TEST_TRUE(gAnyOf(values, [](int v) { return v > 3; })); 28 | TEST_FALSE(gAnyOf(values, [](int v) { return v > 5; })); 29 | 30 | TEST_TRUE(gNoneOf(values, [](int v) { return v > 5; })); 31 | TEST_FALSE(gNoneOf(values, [](int v) { return v > 3; })); 32 | 33 | TEST_TRUE(gAllOf(values, [](int v) { return v <= 5; })); 34 | TEST_FALSE(gAllOf(values, [](int v) { return v < 3; })); 35 | }; 36 | -------------------------------------------------------------------------------- /Bedrock/Trace.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | static TraceCallback sTraceCallback = nullptr; 10 | 11 | namespace Details 12 | { 13 | // The va_list version is kept out of the header to avoid including stdarg.h in the header (or defining it manually) for now. 14 | void StringFormat(StringFormatCallback inAppendCallback, void* outString, const char* inFormat, va_list inArgs); 15 | } 16 | 17 | 18 | void Details::Trace(const char* inFormat, ...) 19 | { 20 | va_list args; 21 | va_start(args, inFormat); 22 | 23 | TempString string = gTempFormatV(inFormat, args); 24 | 25 | va_end(args); 26 | 27 | // If there's a callback, use it. 28 | if (sTraceCallback) 29 | { 30 | sTraceCallback(string); 31 | } 32 | else 33 | { 34 | // Otherwise just print to stdout. 35 | puts(string.AsCStr()); 36 | } 37 | } 38 | 39 | 40 | void gSetTraceCallback(TraceCallback inCallback) 41 | { 42 | gAssert(sTraceCallback == nullptr || inCallback == nullptr); // A callback is already set? 43 | 44 | sTraceCallback = inCallback; 45 | } 46 | -------------------------------------------------------------------------------- /Bedrock/Assert.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef ASSERTS_ENABLED 8 | 9 | static thread_local bool sInsideAssert = false; 10 | 11 | bool gReportAssert(const char* inCondition, const char* inFile, int inLine) 12 | { 13 | // If gReportAssert triggers an assert, early out to avoid infinite recursion. 14 | if (sInsideAssert) 15 | return false; 16 | 17 | sInsideAssert = true; 18 | defer { sInsideAssert = false; }; 19 | 20 | // If we are running a test, fail that test. 21 | if (gIsRunningTest()) 22 | { 23 | gFailTest("gAssert", inCondition, gGetFileNamePart(inFile), inLine); 24 | 25 | // Don't break during tests, there's already a breakpoint inside gFailTest if a debugger is attached. 26 | return false; 27 | } 28 | 29 | // Log the assert. 30 | // TODO: log the entire callstack instead 31 | // TODO: add log type error 32 | gTrace("gAssert(%s) broke (%s:%d).", inCondition, gGetFileNamePart(inFile), inLine); 33 | 34 | return true; 35 | } 36 | 37 | #endif 38 | 39 | 40 | void gCrash(const char* inMessage) 41 | { 42 | gTrace("%s", inMessage); 43 | 44 | CRASH; 45 | } 46 | -------------------------------------------------------------------------------- /Bedrock/Mutex.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | 6 | #define VC_EXTRALEAN 7 | #define WIN32_LEAN_AND_MEAN 8 | #define NOMINMAX 9 | #include 10 | 11 | 12 | Mutex::Mutex() 13 | { 14 | InitializeSRWLock((PSRWLOCK)&mOSMutex); 15 | } 16 | 17 | 18 | Mutex::~Mutex() 19 | { 20 | } 21 | 22 | 23 | void Mutex::Lock() 24 | { 25 | #ifdef ASSERTS_ENABLED 26 | uint32 current_thread_id = GetCurrentThreadId(); 27 | gAssert(mLockingThreadID != current_thread_id); // Recursive locking is not allowed. 28 | #endif 29 | 30 | AcquireSRWLockExclusive((PSRWLOCK)&mOSMutex); 31 | 32 | #ifdef ASSERTS_ENABLED 33 | mLockingThreadID = current_thread_id; 34 | #endif 35 | } 36 | 37 | 38 | void Mutex::Unlock() 39 | { 40 | #ifdef ASSERTS_ENABLED 41 | gAssert(mLockingThreadID == GetCurrentThreadId()); 42 | mLockingThreadID = cInvalidThreadID; 43 | #endif 44 | 45 | ReleaseSRWLockExclusive((PSRWLOCK)&mOSMutex); 46 | } 47 | 48 | 49 | 50 | REGISTER_TEST("Mutex") 51 | { 52 | Mutex mutex; 53 | 54 | mutex.Lock(); 55 | mutex.Unlock(); 56 | 57 | { 58 | LockGuard lock(mutex); 59 | 60 | LockGuard other_lock; 61 | 62 | other_lock = gMove(lock); 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /Bedrock/Time.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | 6 | enum class NanoSeconds : int64; 7 | 8 | // Litterals for time durations. 9 | consteval NanoSeconds operator ""_NS(unsigned long long inValue) { return (NanoSeconds)inValue; } 10 | consteval NanoSeconds operator ""_US(unsigned long long inValue) { return (NanoSeconds)(inValue * 1'000); } 11 | consteval NanoSeconds operator ""_US(long double inValue) { return (NanoSeconds)(inValue * 1'000.0); } 12 | consteval NanoSeconds operator ""_MS(unsigned long long inValue) { return (NanoSeconds)(inValue * 1'000'000); } 13 | consteval NanoSeconds operator ""_MS(long double inValue) { return (NanoSeconds)(inValue * 1'000'000.0); } 14 | consteval NanoSeconds operator ""_S(unsigned long long inValue) { return (NanoSeconds)(inValue * 1'000'000'000); } 15 | consteval NanoSeconds operator ""_S(long double inValue) { return (NanoSeconds)(inValue * 1'000'000'000.0); } 16 | 17 | constexpr double gToMicroSeconds(NanoSeconds inNanoSeconds) { return (double)inNanoSeconds / 1'000.0; } 18 | constexpr double gToMilliSeconds(NanoSeconds inNanoSeconds) { return (double)inNanoSeconds / 1'000'000.0; } 19 | constexpr double gToSeconds(NanoSeconds inNanoSeconds) { return (double)inNanoSeconds / 1'000'000'000.0; } 20 | -------------------------------------------------------------------------------- /Bedrock/Mutex.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | 6 | struct ConditionVariable; 7 | 8 | using OSMutex = void*; 9 | using OSThread = void*; 10 | 11 | 12 | struct Mutex : NoCopy 13 | { 14 | Mutex(); 15 | ~Mutex(); 16 | 17 | void Lock(); 18 | void Unlock(); 19 | 20 | private: 21 | static constexpr uint32 cInvalidThreadID = 0; 22 | 23 | OSMutex mOSMutex = nullptr; 24 | #ifdef ASSERTS_ENABLED 25 | uint32 mLockingThreadID = cInvalidThreadID; 26 | #endif 27 | 28 | friend struct ConditionVariable; 29 | }; 30 | 31 | 32 | template 33 | struct LockGuard : NoCopy 34 | { 35 | LockGuard() = default; 36 | LockGuard(taMutex& ioMutex) 37 | { 38 | mMutex = &ioMutex; 39 | mMutex->Lock(); 40 | } 41 | 42 | ~LockGuard() 43 | { 44 | if (mMutex) 45 | mMutex->Unlock(); 46 | } 47 | 48 | LockGuard(LockGuard&& ioOther) 49 | { 50 | mMutex = ioOther.mMutex; 51 | ioOther.mMutex = nullptr; 52 | } 53 | LockGuard& operator=(LockGuard&& ioOther) 54 | { 55 | mMutex = ioOther.mMutex; 56 | ioOther.mMutex = nullptr; 57 | return *this; 58 | } 59 | 60 | void Unlock() 61 | { 62 | mMutex->Unlock(); 63 | } 64 | 65 | const Mutex* GetMutex() const { return mMutex; } 66 | 67 | private: 68 | taMutex* mMutex = nullptr; 69 | }; 70 | 71 | using MutexLockGuard = LockGuard; -------------------------------------------------------------------------------- /Bedrock/Function.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | 5 | 6 | 7 | REGISTER_TEST("Function") 8 | { 9 | struct CaptureTest 10 | { 11 | CaptureTest() = default; 12 | CaptureTest(const CaptureTest& o) 13 | { 14 | mCopy = o.mCopy + 1; 15 | mMove = o.mMove; 16 | } 17 | CaptureTest(CaptureTest&& o) 18 | { 19 | mCopy = o.mCopy; 20 | mMove = o.mMove + 1; 21 | } 22 | 23 | CaptureTest& operator=(const CaptureTest&) = delete; 24 | CaptureTest& operator=(CaptureTest&&) = delete; 25 | 26 | int8 mCopy = 0; 27 | int8 mMove = 0; 28 | }; 29 | 30 | CaptureTest var0, var1, var2; 31 | 32 | Function func = [&captured_ref = var0, captured_copy = var1, captured_move = gMove(var2)](int inExtraMoves) { 33 | 34 | TEST_TRUE(captured_ref.mMove == 0); 35 | TEST_TRUE(captured_ref.mCopy == 0); 36 | 37 | // One move to Function::mStorage 38 | TEST_TRUE(captured_copy.mMove == 1 + inExtraMoves); 39 | TEST_TRUE(captured_copy.mCopy == 1); 40 | 41 | // One move to the captured variable, then one move to Function::mStorage. 42 | TEST_TRUE(captured_move.mMove == 2 + inExtraMoves); 43 | TEST_TRUE(captured_move.mCopy == 0); 44 | }; 45 | 46 | func(0); 47 | 48 | Function func2 = gMove(func); 49 | 50 | TEST_FALSE(func.IsValid()); 51 | TEST_TRUE(func2.IsValid()); 52 | 53 | func2(1); 54 | }; 55 | 56 | -------------------------------------------------------------------------------- /.github/workflows/msbuild.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Tests 7 | 8 | on: 9 | push: 10 | branches: [ "main" ] 11 | pull_request: 12 | branches: [ "main" ] 13 | 14 | env: 15 | # Path to the solution file relative to the root of the project. 16 | SOLUTION_FILE_PATH: test/BedrockTest.sln 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | build: 23 | name: Build 24 | runs-on: windows-latest 25 | strategy: 26 | matrix: 27 | config: [Debug, DebugOpt, Release] 28 | platform: [x64, Clang] 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | with: 33 | submodules: true 34 | 35 | - name: Generate Solution 36 | shell: cmd 37 | run: cd test && premake.bat 38 | 39 | - name: Add MSBuild to PATH 40 | uses: microsoft/setup-msbuild@v1.0.2 41 | 42 | - name: Build 43 | run: msbuild /m /p:Configuration="${{ matrix.config }}" /p:Platform=${{ matrix.platform }} ${{env.SOLUTION_FILE_PATH}} 44 | 45 | # Skip the tests in Release 46 | - if: ${{ !startsWith(matrix.config, 'Release') }} 47 | shell: cmd 48 | run: .\test\bin\${{ matrix.platform }}\${{ matrix.config }}\BedrockTest${{ matrix.config }}.exe 49 | 50 | 51 | -------------------------------------------------------------------------------- /Bedrock/MemoryArena.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | 5 | 6 | 7 | REGISTER_TEST("MemArena") 8 | { 9 | alignas(MemArena<>::cAlignment) uint8 buffer[MemArena<>::cAlignment * 5]; 10 | MemArena arena({ buffer, sizeof(buffer) }); 11 | 12 | MemBlock b1 = arena.Alloc(1); 13 | TEST_TRUE(b1 != nullptr); 14 | MemBlock b2 = arena.Alloc(1); 15 | TEST_TRUE(b2 != nullptr); 16 | MemBlock b3 = arena.Alloc(1); 17 | TEST_TRUE(b3 != nullptr); 18 | MemBlock b4 = arena.Alloc(1); 19 | TEST_TRUE(b4 != nullptr); 20 | MemBlock b5 = arena.Alloc(1); 21 | TEST_TRUE(b5 != nullptr); 22 | 23 | MemBlock b6 = arena.Alloc(1); 24 | TEST_TRUE(b6 == nullptr); 25 | 26 | arena.Free(b4); 27 | TEST_TRUE(arena.GetAllocatedSize() == arena.GetMemBlock().mSize); 28 | TEST_TRUE(arena.GetNumPendingFree() == 1); 29 | arena.Free(b2); 30 | TEST_TRUE(arena.GetAllocatedSize() == arena.GetMemBlock().mSize); 31 | TEST_TRUE(arena.GetNumPendingFree() == 2); 32 | 33 | arena.Free(b3); 34 | TEST_TRUE(arena.GetAllocatedSize() == arena.GetMemBlock().mSize); 35 | TEST_TRUE(arena.GetNumPendingFree() == 1); // All free blocks get merged. 36 | 37 | arena.Free(b1); 38 | TEST_TRUE(arena.GetAllocatedSize() == arena.GetMemBlock().mSize); 39 | TEST_TRUE(arena.GetNumPendingFree() == 1); 40 | arena.Free(b5); 41 | TEST_TRUE(arena.GetAllocatedSize() == 0); 42 | TEST_TRUE(arena.GetNumPendingFree() == 0); 43 | }; 44 | -------------------------------------------------------------------------------- /Bedrock/Array.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | template 8 | struct Array 9 | { 10 | using ValueType = taType; 11 | 12 | constexpr static int Size() { return taSize; } 13 | 14 | constexpr const taType* Data() const { return mData; } 15 | constexpr taType* Data() { return mData; } 16 | 17 | constexpr const taType* Begin() const { return mData; } 18 | constexpr const taType* End() const { return mData + taSize; } 19 | constexpr const taType* begin() const { return mData; } 20 | constexpr const taType* end() const { return mData + taSize; } 21 | constexpr taType* Begin() { return mData; } 22 | constexpr taType* End() { return mData + taSize; } 23 | constexpr taType* begin() { return mData; } 24 | constexpr taType* end() { return mData + taSize; } 25 | 26 | constexpr void Fill(const taType& inValue) 27 | { 28 | for (taType& value : mData) 29 | value = inValue; 30 | } 31 | 32 | constexpr const taType& operator[](int inPosition) const { gBoundsCheck(inPosition, taSize); return mData[inPosition]; } 33 | constexpr taType& operator[](int inPosition) { gBoundsCheck(inPosition, taSize); return mData[inPosition]; } 34 | 35 | taType mData[taSize]; 36 | }; 37 | 38 | 39 | // Array is a contiguous container. 40 | template inline constexpr bool cIsContiguous> = true; 41 | -------------------------------------------------------------------------------- /Bedrock/InitializerList.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #ifdef BEDROCK_ENABLE_STD 5 | 6 | #include 7 | 8 | #else 9 | 10 | namespace std 11 | { 12 | template 13 | class initializer_list 14 | { 15 | public: 16 | using value_type = taType; 17 | using reference = const taType&; 18 | using const_reference = const taType&; 19 | using size_type = size_t; 20 | 21 | using iterator = const taType*; 22 | using const_iterator = const taType*; 23 | 24 | constexpr initializer_list() noexcept = default; 25 | 26 | constexpr initializer_list(const taType* inBegin, const taType* inEnd) noexcept 27 | { 28 | mBegin = inBegin; 29 | mEnd = inEnd; 30 | } 31 | 32 | constexpr const taType* begin() const noexcept { return mBegin; } 33 | constexpr const taType* end() const noexcept { return mEnd; } 34 | constexpr size_t size() const noexcept { return mEnd - mBegin; } 35 | 36 | private: 37 | const taType* mBegin = nullptr; 38 | const taType* mEnd = nullptr; 39 | }; 40 | 41 | template 42 | constexpr const taType* begin(initializer_list inInitializerList) noexcept { return inInitializerList.begin(); } 43 | 44 | template 45 | constexpr const taType* end(initializer_list inInitializerList) noexcept { return inInitializerList.end(); } 46 | } 47 | 48 | 49 | #endif 50 | 51 | template 52 | using InitializerList = std::initializer_list; -------------------------------------------------------------------------------- /Bedrock/Storage.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | 8 | // Holds storage for a value but does not construct it until Create() is called. 9 | // This is functionally close to Optional, but the goal is only to delay construction, not pass around optional values. 10 | template 11 | struct Storage : NoCopy 12 | { 13 | // Default 14 | constexpr Storage() {} 15 | constexpr ~Storage() 16 | { 17 | if (mCreated) 18 | Destroy(); 19 | } 20 | 21 | // Construct the internal value. 22 | template 23 | constexpr void Create(taArgs&&... inArgs) 24 | { 25 | gAssert(!mCreated); 26 | mCreated = true; 27 | gPlacementNew(mValue, gForward(inArgs)...); 28 | } 29 | 30 | // Destroy the internal value. 31 | void Destroy() 32 | { 33 | gAssert(mCreated); 34 | mCreated = false; 35 | mValue.~taType(); 36 | } 37 | 38 | force_inline constexpr bool IsCreated() const { return mCreated; } 39 | force_inline constexpr taType* operator->() { gAssert(mCreated); return &mValue; } 40 | force_inline constexpr const taType* operator->() const { gAssert(mCreated); return &mValue; } 41 | force_inline constexpr operator taType*() { gAssert(mCreated); return &mValue; } 42 | force_inline constexpr operator const taType*() const { gAssert(mCreated); return &mValue; } 43 | 44 | private: 45 | union 46 | { 47 | taType mValue; 48 | }; 49 | bool mCreated = false; 50 | }; 51 | 52 | -------------------------------------------------------------------------------- /Bedrock/Semaphore.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | 5 | #define VC_EXTRALEAN 6 | #define WIN32_LEAN_AND_MEAN 7 | #define NOMINMAX 8 | #include 9 | 10 | 11 | Semaphore::Semaphore(int inInitialCount, int inMaxCount) 12 | { 13 | mOSSemaphore = CreateSemaphoreA(nullptr, inInitialCount, inMaxCount, nullptr); 14 | } 15 | 16 | 17 | Semaphore::~Semaphore() 18 | { 19 | bool success = CloseHandle((HANDLE)mOSSemaphore); 20 | gAssert(success); 21 | } 22 | 23 | 24 | bool Semaphore::TryAcquire() 25 | { 26 | return TryAcquireFor(0_NS); 27 | } 28 | 29 | 30 | bool Semaphore::TryAcquireFor(NanoSeconds inTimeout) 31 | { 32 | int timeout_ms = (int)gToMilliSeconds(inTimeout); 33 | 34 | int ret = WaitForSingleObject((HANDLE)mOSSemaphore, timeout_ms); 35 | return ret == WAIT_OBJECT_0; 36 | } 37 | 38 | 39 | void Semaphore::Acquire() 40 | { 41 | int ret = WaitForSingleObject((HANDLE)mOSSemaphore, INFINITE); 42 | gAssert(ret == WAIT_OBJECT_0); 43 | } 44 | 45 | 46 | void Semaphore::Release(int inCount) 47 | { 48 | gAssert(inCount > 0); 49 | 50 | bool success = ReleaseSemaphore((HANDLE)mOSSemaphore, inCount, nullptr); 51 | gAssert(success); // Going above the max count. 52 | } 53 | 54 | 55 | REGISTER_TEST("Semaphore") 56 | { 57 | Semaphore sema(0, 2); 58 | 59 | TEST_FALSE(sema.TryAcquire()); 60 | 61 | sema.Release(2); 62 | sema.Acquire(); 63 | TEST_TRUE(sema.TryAcquireFor(1_MS)); 64 | TEST_FALSE(sema.TryAcquire()); 65 | sema.Release(1); 66 | TEST_TRUE(sema.TryAcquire()); 67 | }; 68 | -------------------------------------------------------------------------------- /Bedrock/Ticks.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | 4 | #define VC_EXTRALEAN 5 | #define WIN32_LEAN_AND_MEAN 6 | #define NOMINMAX 7 | #include 8 | 9 | 10 | // Note: QPC resolution is 100ns, but if we ever need more precision we could use tenths of nanosecond here instead. 11 | static int64 sGetNanosecondsPerTick() 12 | { 13 | static const int64 sNanosecondsPerTick = [] 14 | { 15 | LARGE_INTEGER frequency { .QuadPart = 1 }; 16 | QueryPerformanceFrequency(&frequency); 17 | 18 | return 1'000'000'000 / frequency.QuadPart; 19 | }(); 20 | 21 | return sNanosecondsPerTick; 22 | } 23 | 24 | 25 | int64 gGetTickCount() 26 | { 27 | LARGE_INTEGER counter = {}; 28 | QueryPerformanceCounter(&counter); 29 | return counter.QuadPart; 30 | } 31 | 32 | 33 | int64 gTicksToNanoseconds(int64 inTicks) 34 | { 35 | return inTicks * sGetNanosecondsPerTick(); 36 | } 37 | 38 | 39 | double gTicksToMilliseconds(int64 inTicks) 40 | { 41 | int64 ns = gTicksToNanoseconds(inTicks); 42 | return (double)ns / 1'000'000.0; 43 | } 44 | 45 | 46 | double gTicksToSeconds(int64 inTicks) 47 | { 48 | int64 ns = gTicksToNanoseconds(inTicks); 49 | return (double)ns / 1'000'000'000.0; 50 | } 51 | 52 | 53 | int64 gNanosecondsToTicks(int64 inNanoseconds) 54 | { 55 | return inNanoseconds / sGetNanosecondsPerTick(); 56 | } 57 | 58 | 59 | int64 gMillisecondsToTicks(double inMilliseconds) 60 | { 61 | return gNanosecondsToTicks(int64(inMilliseconds * 1'000'000.0)); 62 | } 63 | 64 | 65 | int64 gSecondsToTicks(double inSeconds) 66 | { 67 | return gNanosecondsToTicks(int64(inSeconds * 1'000'000'000.0)); 68 | } 69 | -------------------------------------------------------------------------------- /Bedrock/Assert.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | // Break to the debugger (or crash if no debugger is attached). 5 | #define BREAKPOINT __debugbreak() 6 | 7 | // Cause a crash. 8 | #ifdef __clang__ 9 | #define CRASH __builtin_trap() 10 | #elif _MSC_VER 11 | extern "C" void __ud2(); 12 | #define CRASH __ud2() 13 | #else 14 | #error Unknown compiler 15 | #endif 16 | 17 | 18 | #ifdef ASSERTS_ENABLED 19 | 20 | // Assert macro. 21 | #define gAssert(condition) \ 22 | do \ 23 | { \ 24 | if (!(condition)) [[unlikely]] \ 25 | if (gReportAssert(#condition, __FILE__, __LINE__)) [[likely]] \ 26 | BREAKPOINT; \ 27 | } while (0) 28 | 29 | 30 | // Verify macro. Similar to assert, but condition is still executed when asserts are disabled. 31 | #define gVerify(condition) gAssert(condition) 32 | 33 | 34 | // Internal assert report function. Return true if it should break. 35 | bool gReportAssert(const char* inCondition, const char* inFile, int inLine); 36 | 37 | #else 38 | 39 | #define gAssert(condition) do { (void)sizeof(!(condition)); } while(0) 40 | #define gVerify(condition) do { (void)(condition); } while(0) 41 | 42 | #endif 43 | 44 | // Bound checking helper macro. 45 | #define gBoundsCheck(index, size) gAssert((index) >= 0 && (index) < (size)) 46 | 47 | 48 | // Print a message then crash. 49 | [[noreturn]] void gCrash(const char* inMessage); 50 | -------------------------------------------------------------------------------- /Bedrock/Compare.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | 4 | namespace 5 | { 6 | struct CompareTest 7 | { 8 | int i, j; 9 | auto operator<=>(const CompareTest&) const = default; 10 | }; 11 | 12 | constexpr CompareTest a = { 1, 3 }; 13 | constexpr CompareTest b = { 3, 1 }; 14 | constexpr CompareTest c = { 3, 1 }; 15 | 16 | static_assert((a <=> b) < 0); 17 | static_assert((a <=> b) == StrongOrdering::less); 18 | 19 | static_assert((b <=> a) > 0); 20 | static_assert((b <=> a) == StrongOrdering::greater); 21 | 22 | static_assert((b <=> c) == 0); 23 | static_assert((b <=> c) == StrongOrdering::equal); 24 | static_assert((b <=> c) == StrongOrdering::equivalent); 25 | 26 | static_assert((c <=> b) == 0); 27 | static_assert((c <=> b) == StrongOrdering::equal); 28 | static_assert((c <=> b) == StrongOrdering::equivalent); 29 | 30 | constexpr float cNan = __builtin_nanf(""); 31 | 32 | static_assert((1.0f <=> 3.0f) < 0); 33 | static_assert((1.0f <=> 3.0f) == PartialOrdering::less); 34 | 35 | static_assert((3.0f <=> 1.0f) > 0); 36 | static_assert((3.0f <=> 1.0f) > 0); 37 | static_assert((3.0f <=> 1.0f) == PartialOrdering::greater); 38 | 39 | static_assert((3.0f <=> 3.0f) == 0); 40 | static_assert((3.0f <=> 3.0f) == PartialOrdering::equivalent); 41 | 42 | static_assert((cNan <=> 1) == PartialOrdering::unordered); 43 | static_assert(!((cNan <=> 1) == 0)); 44 | static_assert(!((cNan <=> 1) <= 0)); 45 | static_assert(!((cNan <=> 1) >= 0)); 46 | static_assert(!((cNan <=> 1) > 0)); 47 | static_assert(!((cNan <=> 1) > 0)); 48 | static_assert((-0.0f <=> 0.0f) == PartialOrdering::equivalent); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /Bedrock/FunctionRef.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | template struct FunctionRef; 9 | 10 | // A non-owning polymorphic function wrapper. Roughly equivalent to std::function_ref. 11 | // Unlike Function, it doesn't store a copy of the lambda, only a pointer to it. 12 | template 13 | struct FunctionRef 14 | { 15 | // Default 16 | FunctionRef() = default; 17 | ~FunctionRef() = default; 18 | 19 | // Copy 20 | FunctionRef(const FunctionRef&) = default; 21 | FunctionRef& operator=(const FunctionRef&) = default; 22 | 23 | template 24 | requires (!cIsSame>, FunctionRef>) // Don't accept FunctionRef types, this is not a copy-constructor. 25 | FunctionRef(taLambda&& ioLambda ATTRIBUTE_LIFETIMEBOUND) 26 | { 27 | mPointer = (void*)&ioLambda; 28 | mInvoke = [](void* ioPointer, taArgs&&... ioArgs) { return (*(RemoveReference*)ioPointer)(gForward(ioArgs)...); }; 29 | } 30 | 31 | bool IsValid() const { return mInvoke != nullptr; } 32 | 33 | [[nodiscard]] taResult operator()(taArgs... ioArgs) 34 | { 35 | gAssert(IsValid()); 36 | return mInvoke(mPointer, gForward(ioArgs)...); 37 | } 38 | 39 | private: 40 | using InvokeFunc = taResult (*)(void*, taArgs&&...); 41 | 42 | void* mPointer = nullptr; 43 | InvokeFunc mInvoke = nullptr; 44 | }; 45 | 46 | 47 | // Deduction guide for function pointers. 48 | template 49 | FunctionRef(taResult (*)(taArgs...)) -> FunctionRef; 50 | -------------------------------------------------------------------------------- /Bedrock/UniquePtr.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | 6 | constexpr void gDefaultDelete(auto* inValue) { delete inValue; }; 7 | 8 | 9 | // Very minimal, deleter is a function only, no array support for now. 10 | template > 11 | struct UniquePtr 12 | { 13 | // Default 14 | constexpr UniquePtr() = default; 15 | constexpr ~UniquePtr() 16 | { 17 | taDeleter(mPtr); 18 | } 19 | 20 | // No copy 21 | UniquePtr(const UniquePtr&) = delete; 22 | UniquePtr& operator=(const UniquePtr&) = delete; 23 | 24 | // Move 25 | constexpr UniquePtr(UniquePtr&& ioOther) 26 | { 27 | mPtr = ioOther.mPtr; 28 | ioOther.mPtr = nullptr; 29 | } 30 | constexpr UniquePtr& operator=(UniquePtr&& ioOther) 31 | { 32 | // Early out if moving to self 33 | if (this == &ioOther) 34 | return *this; 35 | 36 | taDeleter(mPtr); 37 | mPtr = ioOther.mPtr; 38 | ioOther.mPtr = nullptr; 39 | return *this; 40 | } 41 | 42 | // From pointer 43 | constexpr explicit UniquePtr(taType* inPtr) : mPtr(inPtr) {} 44 | 45 | // From nullptr 46 | constexpr UniquePtr(NullPtrType) {} 47 | 48 | constexpr taType* Get() const { return mPtr; } 49 | constexpr taType* operator->() const { return mPtr; } 50 | constexpr operator taType*() const& { return mPtr; } 51 | constexpr operator taType*() const&& = delete; // Dangerous: The pointer would probably be deleted by the time you use it 52 | constexpr explicit operator bool() const { return mPtr != nullptr; } 53 | 54 | taType* Detach() const 55 | { 56 | taType* ptr = mPtr; 57 | mPtr = nullptr; 58 | return ptr; 59 | } 60 | 61 | private: 62 | taType* mPtr = nullptr; 63 | }; 64 | 65 | 66 | -------------------------------------------------------------------------------- /Bedrock/Event.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | 5 | #define VC_EXTRALEAN 6 | #define WIN32_LEAN_AND_MEAN 7 | #define NOMINMAX 8 | #include 9 | 10 | 11 | Event::Event(ResetMode inResetMode, bool inInitialState) 12 | { 13 | mOSEvent = CreateEventA(nullptr, inResetMode == ManualReset, inInitialState, nullptr); 14 | gAssert(mOSEvent != nullptr); 15 | } 16 | 17 | 18 | Event::~Event() 19 | { 20 | bool success = CloseHandle((HANDLE)mOSEvent); 21 | gAssert(success); 22 | } 23 | 24 | 25 | void Event::Set() 26 | { 27 | bool success = SetEvent((HANDLE)mOSEvent); 28 | gAssert(success); 29 | } 30 | 31 | 32 | void Event::Reset() 33 | { 34 | bool success = ResetEvent((HANDLE)mOSEvent); 35 | gAssert(success); 36 | } 37 | 38 | 39 | bool Event::TryWait() const 40 | { 41 | return TryWaitFor(0_NS); 42 | } 43 | 44 | 45 | bool Event::TryWaitFor(NanoSeconds inTimeout) const 46 | { 47 | int timeout_ms = (int)gToMilliSeconds(inTimeout); 48 | 49 | int ret = WaitForSingleObject((HANDLE)mOSEvent, timeout_ms); 50 | return ret == WAIT_OBJECT_0; 51 | } 52 | 53 | 54 | void Event::Wait() const 55 | { 56 | int ret = WaitForSingleObject((HANDLE)mOSEvent, INFINITE); 57 | gAssert(ret == WAIT_OBJECT_0); 58 | } 59 | 60 | 61 | REGISTER_TEST("Event") 62 | { 63 | { 64 | Event event(Event::ManualReset, false); 65 | 66 | TEST_FALSE(event.TryWait()); 67 | 68 | event.Set(); 69 | TEST_TRUE(event.TryWait()); 70 | 71 | event.Reset(); 72 | TEST_FALSE(event.TryWait()); 73 | } 74 | 75 | { 76 | Event event(Event::AutoReset, false); 77 | 78 | TEST_FALSE(event.TryWait()); 79 | 80 | event.Set(); 81 | TEST_TRUE(event.TryWait()); 82 | 83 | TEST_FALSE(event.TryWait()); 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /Bedrock/Thread.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | using OSThread = void*; 11 | 12 | struct Thread; 13 | 14 | enum class EThreadPriority : int8 15 | { 16 | Idle, 17 | Lowest, 18 | BelowNormal, 19 | Normal, 20 | AboveNormal, 21 | Highest 22 | }; 23 | 24 | 25 | struct ThreadConfig 26 | { 27 | String mName = ""; // The thread name. 28 | int mStackSize = 128_KiB; // The stack size of the thread. 29 | int mTempMemSize = 128_KiB; // Initialize a temp memory of that size of the thread. Can be 0. 30 | EThreadPriority mPriority = EThreadPriority::Normal; // The priority of the thread. 31 | }; 32 | 33 | 34 | struct Thread : NoCopy 35 | { 36 | // Default 37 | Thread() = default; 38 | ~Thread(); 39 | 40 | void Create(const ThreadConfig& inConfig, Function&& ioEntryPoint); 41 | void Join(); 42 | bool IsJoinable() const { return mOSThread != nullptr; } 43 | 44 | void RequestStop() { mStopRequested.Store(true); } 45 | bool IsStopRequested() const { return mStopRequested.Load(); } 46 | 47 | const ThreadConfig& GetConfig() const { return mConfig; } 48 | 49 | private: 50 | void Cleanup(); 51 | 52 | Function mEntryPoint; 53 | ThreadConfig mConfig = {}; 54 | OSThread mOSThread = {}; 55 | uint32 mOSThreadID = 0; 56 | AtomicBool mStopRequested = false; 57 | 58 | friend struct ThreadInternal; 59 | }; 60 | 61 | 62 | // Yield the processor to other threads that are ready to run. 63 | void gThreadYield(); 64 | 65 | // Number of threads that can run concurrently. 66 | // Equivalent to the number of CPU cores (incuding hyperthreading logical cores). 67 | int gThreadHardwareConcurrency(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bedrock 🪨 2 | 3 | Bedrock is a C++20 STL alternative. Smaller, simpler, in many case faster. It's a hobby project, don't expect much more than an interesting implementation reference for things. 4 | 5 | Currently Windows only. Supports MSVC and Clang. There are no concrete plans to support more platforms/compilers at this time. 6 | 7 | ## Containers and Views 8 | 9 | ```c++ 10 | Vector // Roughly equivalent to std::vector, with extra useful methods (Find, SwapErase, etc.) 11 | Span // Roughly equivalent to std::span 12 | String // Roughly equivalent to std::string 13 | StringView // Roughly equivalent to std::string_view 14 | HashMap // Dense open addressed (Robin Hood) hash map. Key-value pairs are stored contiguously. 15 | HashSet // Same as HashMap, but without values. 16 | ``` 17 | 18 | ## Allocators 19 | 20 | All containers come in different allocator flavors: 21 | 22 | ```c++ 23 | TempVector // Allocates from a thread local arena. Falls back to the heap if it runs out. 24 | FixedVector // Allocates from a fixed-size arena embedded in the container. 25 | VMemVector // Allocates from a virtual memory arena embedded in the container. Can grow while keeping a stable address. 26 | ArenaVector // Allocates from an externally provided arena. 27 | 28 | ``` 29 | 30 | ## Tests 31 | 32 | Write tests anywhere: 33 | 34 | ```c++ 35 | REGISTER_TEST("Span") 36 | { 37 | int values[] = { 1, 2, 3, 4, 5 }; 38 | Span test = values; 39 | 40 | Span first_two = test.First(2); 41 | TEST_TRUE(first_two.Size() == 2); 42 | TEST_TRUE(first_two[0] == 1); 43 | TEST_TRUE(first_two[1] == 2); 44 | }; 45 | ``` 46 | 47 | The run them with `gRunAllTests()`. 48 | 49 | ## Other 50 | 51 | Mutex, Atomic, Thread, Semaphore. 52 | Function, many Type Traits, a few Algorithms... 53 | 54 | ## Building 55 | 56 | Compile every cpp file in Bedrock/. Define `ASSERTS_ENABLED` if you want asserts and tests. That's about it. 57 | -------------------------------------------------------------------------------- /test/premake.lua: -------------------------------------------------------------------------------- 1 | solution "BedrockTest" 2 | 3 | platforms { "x64", "Clang" } 4 | configurations { "Debug", "DebugASAN", "DebugOpt", "Release" } 5 | startproject "BedrockTest" 6 | 7 | project "BedrockTest" 8 | 9 | kind "ConsoleApp" 10 | symbols "On" 11 | cppdialect "C++latest" 12 | exceptionhandling "Off" 13 | rtti "Off" 14 | architecture "x64" 15 | flags 16 | { 17 | "MultiProcessorCompile", 18 | "FatalWarnings" 19 | } 20 | 21 | buildoptions 22 | { 23 | "/utf-8" 24 | } 25 | 26 | filter { "platforms:Clang" } 27 | toolset "clang" 28 | 29 | filter { "configurations:Debug" } 30 | targetsuffix "Debug" 31 | defines "ASSERTS_ENABLED" 32 | optimize "Debug" 33 | runtime "Debug" 34 | editandcontinue "On" 35 | 36 | filter { "configurations:DebugASAN" } 37 | targetsuffix "DebugASAN" 38 | defines "ASSERTS_ENABLED" 39 | optimize "Debug" 40 | runtime "Debug" 41 | editandcontinue "Off" -- incompatible with ASAN 42 | flags "NoIncrementalLink" -- incompatible with ASAN 43 | sanitize "Address" 44 | 45 | -- Note: ASAN + Clang is not working. This is enough to make it link, but it doesn't run. 46 | -- filter { "configurations:DebugASAN", "platforms:Clang" } 47 | -- runtime "Release" -- debug runtime is incompatible with ASAN (clang-cl) 48 | -- links 49 | -- { 50 | -- -- apparently VS doesn't automatically link the libs when using ASAN with clang-cl 51 | -- "clang_rt.asan_dynamic-x86_64", 52 | -- "clang_rt.asan_dynamic_runtime_thunk-x86_64" 53 | -- } 54 | 55 | filter { "configurations:DebugOpt" } 56 | targetsuffix "DebugOpt" 57 | defines "ASSERTS_ENABLED" 58 | optimize "Full" 59 | editandcontinue "On" 60 | 61 | filter { "configurations:Release" } 62 | optimize "Full" 63 | 64 | filter {} 65 | 66 | vpaths { ["*"] = ".." } -- get rid of the .. folder in the vstudio solution explorer 67 | 68 | files 69 | { 70 | "../Bedrock/*.h", 71 | "../Bedrock/*.cpp", 72 | "../Bedrock/*.natvis", 73 | "../Bedrock/*.natstepfilter", 74 | "../.clang-format", 75 | "../.editorconfig", 76 | "Main.cpp" 77 | } 78 | 79 | includedirs 80 | { 81 | "..", 82 | } 83 | -------------------------------------------------------------------------------- /Bedrock/Bedrock.natvis: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ Size={mSize} }} 6 | 7 | mSize 8 | 9 | mSize 10 | mData 11 | 12 | 13 | 14 | 15 | 16 | 17 | {{ Size={mSize} }} 18 | 19 | mSize 20 | mCapacity 21 | 22 | mSize 23 | mData 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{ Size={$T2} }} 31 | 32 | 33 | $T2 34 | mData 35 | 36 | 37 | 38 | 39 | 40 | 41 | { mData, [mSize]s8} 42 | mData, [mSize]s8 43 | 44 | 45 | 46 | 47 | 48 | mDistanceAndFingerprint / cDistanceIncrement 49 | (char)(mDistanceAndFingerprint & cFingerprintMask), nvoxb 50 | mKeyValueIndex 51 | 52 | 53 | 54 | 55 | 56 | { mValue } 57 | 58 | 59 | 60 | 61 | mPtr 62 | nullptr 63 | UniquePtr {*mPtr} 64 | 65 | mPtr 66 | 67 | 68 | 69 | 70 | 71 | &mValue 72 | Uninitialized 73 | { mValue } 74 | 75 | mValue 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Bedrock/Hash.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | // Let's save including intrin.h for just one function. 8 | #if defined(_MSC_VER) && defined(_M_X64) && !defined(_M_ARM64EC) 9 | extern "C" unsigned __int64 _umul128(unsigned __int64 _Multiplier, unsigned __int64 _Multiplicand, unsigned __int64* _HighProduct); 10 | #pragma intrinsic(_umul128) 11 | #endif 12 | 13 | // We don't want the includes for the standard integer types but also we won't want conflicts if are already defined, 14 | // so put everything inside a namespace. 15 | namespace Details::Rapidhash 16 | { 17 | using uint64_t = uint64; 18 | using uint32_t = uint32; 19 | using uint8_t = uint8; 20 | 21 | #define RAPIDHASH_NO_INCLUDES 22 | #include 23 | } 24 | 25 | constexpr uint64 cHashSeed = RAPID_SEED; 26 | 27 | inline uint64 gHash(const void* inPtr, int inSize, uint64 inSeed = cHashSeed) 28 | { 29 | return Details::Rapidhash::rapidhash_withSeed(inPtr, inSize, inSeed); 30 | } 31 | 32 | template struct Hash; 33 | 34 | // True if this Hash type supports hashing multiple equivalent types. 35 | // eg. Hash is transparent and allows hashing const char* as well. 36 | template 37 | concept cIsTransparent = requires { typename taHash::IsTransparent; }; 38 | 39 | // Very fast hash for integer types. CAVEAT: returns 0 for a value of 0! 40 | template inline uint64 gHash(taType inValue) 41 | { 42 | // Note: Seed is not exposed here because combining anything with 0 would return 0. 43 | return Details::Rapidhash::rapid_mix((uint64)inValue, cHashSeed); 44 | } 45 | 46 | // Slighly slower hash for integer types that allows a seed (useful to combine multiple hashes). 47 | template inline uint64 gHash(taType inValue, uint64 inSeed) 48 | { 49 | return Details::Rapidhash::rapidhash_withSeed(&inValue, sizeof(inValue), inSeed); 50 | } 51 | 52 | // Hash struct specialization for integers. To use with HashMap/HashSet. 53 | template 54 | struct Hash 55 | { 56 | uint64 operator()(taType inValue) const 57 | { 58 | return gHash(inValue); 59 | } 60 | }; 61 | 62 | // Hash struct specialization for pointers. To use with HashMap/HashSet. 63 | template 64 | struct Hash 65 | { 66 | uint64 operator()(taType* inValue) const 67 | { 68 | return gHash((uint64)inValue); 69 | } 70 | }; 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Bedrock/Memory.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | MemBlock gMemAlloc(int64 inSize) 9 | { 10 | MemBlock memory = { (uint8*)malloc(inSize), inSize }; 11 | 12 | #ifdef TESTS_ENABLED 13 | if (gIsRunningTest()) 14 | gRegisterAlloc(memory); 15 | #endif 16 | 17 | return memory; 18 | } 19 | 20 | 21 | void gMemFree(MemBlock inMemory) 22 | { 23 | gAssert(inMemory.mPtr != nullptr); 24 | gAssert(inMemory.mSize > 0); 25 | 26 | #ifdef TESTS_ENABLED 27 | if (gIsRunningTest()) 28 | gRegisterFree(inMemory); 29 | #endif 30 | 31 | free(inMemory.mPtr); 32 | } 33 | 34 | 35 | int gVMemReserveGranularity() 36 | { 37 | static const int sReserveGranularity = []() 38 | { 39 | SYSTEM_INFO sys_info; 40 | GetSystemInfo(&sys_info); 41 | 42 | gAssert(gIsPow2(sys_info.dwAllocationGranularity)); 43 | return (int)sys_info.dwAllocationGranularity; 44 | }(); 45 | 46 | return sReserveGranularity; 47 | } 48 | 49 | 50 | int gVMemCommitGranularity() 51 | { 52 | static const int sCommitGranularity = []() 53 | { 54 | SYSTEM_INFO sys_info; 55 | GetSystemInfo(&sys_info); 56 | 57 | gAssert(gIsPow2(sys_info.dwPageSize)); 58 | return (int)sys_info.dwPageSize; 59 | }(); 60 | 61 | return sCommitGranularity; 62 | } 63 | 64 | 65 | MemBlock gVMemReserve(int64 inSize) 66 | { 67 | inSize = gAlignUp(inSize, gVMemReserveGranularity()); 68 | void* ptr = VirtualAlloc(nullptr, inSize, MEM_RESERVE, PAGE_NOACCESS); 69 | 70 | if (ptr == nullptr) [[unlikely]] 71 | { 72 | gAssert(false); 73 | return {}; 74 | } 75 | 76 | return { (uint8*)ptr, inSize }; 77 | } 78 | 79 | 80 | void gVMemFree(MemBlock inBlock) 81 | { 82 | BOOL success = VirtualFree(inBlock.mPtr, 0, MEM_RELEASE); 83 | gAssert(success); 84 | } 85 | 86 | 87 | MemBlock gVMemCommit(MemBlock inBlock) 88 | { 89 | int64 begin = (int64)inBlock.mPtr; 90 | int64 end = begin + inBlock.mSize; 91 | 92 | // Align the memory block boundaries to page sizes (in case they weren't). 93 | int64 granularity = gVMemCommitGranularity(); 94 | begin = gAlignDown(begin, granularity); 95 | end = gAlignUp(end, granularity); 96 | 97 | inBlock.mPtr = (uint8*)begin; 98 | inBlock.mSize = end - begin; 99 | 100 | void* ptr = VirtualAlloc(inBlock.mPtr, inBlock.mSize, MEM_COMMIT, PAGE_READWRITE); 101 | 102 | if (ptr == nullptr) [[unlikely]] 103 | { 104 | gAssert(false); 105 | return {}; 106 | } 107 | 108 | return inBlock; 109 | } -------------------------------------------------------------------------------- /Bedrock/ConditionVariable.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | 6 | #define VC_EXTRALEAN 7 | #define WIN32_LEAN_AND_MEAN 8 | #define NOMINMAX 9 | #include 10 | 11 | 12 | ConditionVariable::ConditionVariable() 13 | { 14 | InitializeConditionVariable((PCONDITION_VARIABLE)&mOSCondVar); 15 | } 16 | 17 | 18 | ConditionVariable::~ConditionVariable() 19 | { 20 | } 21 | 22 | 23 | void ConditionVariable::NotifyOne() 24 | { 25 | WakeConditionVariable((PCONDITION_VARIABLE)&mOSCondVar); 26 | } 27 | 28 | 29 | void ConditionVariable::NotifyAll() 30 | { 31 | WakeAllConditionVariable((PCONDITION_VARIABLE)&mOSCondVar); 32 | } 33 | 34 | 35 | ConditionVariable::WaitResult ConditionVariable::Wait(MutexLockGuard& ioLock, NanoSeconds inTimeout) 36 | { 37 | int timeout_ms = (inTimeout == cInfiniteTimeout) ? INFINITE : (int)gToMilliSeconds(inTimeout); 38 | 39 | #ifdef ASSERTS_ENABLED 40 | // Update the locking thread ID stored inside the mutex, waiting is going to unlock it. 41 | Mutex* mutex = const_cast(ioLock.GetMutex()); 42 | uint32 locking_thread_id = mutex->mLockingThreadID; 43 | mutex->mLockingThreadID = Mutex::cInvalidThreadID; 44 | #endif 45 | 46 | BOOL ret = SleepConditionVariableSRW( 47 | (PCONDITION_VARIABLE)&mOSCondVar, 48 | (PSRWLOCK)&ioLock.GetMutex()->mOSMutex, 49 | timeout_ms, 50 | 0); 51 | 52 | #ifdef ASSERTS_ENABLED 53 | // Put the locking thread ID back. 54 | gAssert(mutex->mLockingThreadID == Mutex::cInvalidThreadID); 55 | mutex->mLockingThreadID = locking_thread_id; 56 | #endif 57 | 58 | if (ret != 0) 59 | return WaitResult::Success; 60 | 61 | gAssert(GetLastError() == ERROR_TIMEOUT); 62 | return WaitResult::Timeout; 63 | } 64 | 65 | 66 | REGISTER_TEST("ConditionVariable") 67 | { 68 | ConditionVariable cond; 69 | Mutex mutex; 70 | int shared_value = 0; 71 | 72 | LockGuard lock = mutex; 73 | 74 | Thread thread; 75 | thread.Create({}, [&cond, &mutex, &shared_value](Thread&) 76 | { 77 | LockGuard lock = mutex; 78 | 79 | while (shared_value < 10) 80 | { 81 | // Wait until the value is even, then increment it. 82 | while ((shared_value & 1) == 0) 83 | cond.Wait(lock); 84 | 85 | shared_value++; 86 | cond.NotifyOne(); // Notify the other thread. 87 | } 88 | }); 89 | 90 | while (shared_value < 10) 91 | { 92 | // Wait until the value is odd, then increment it. 93 | while ((shared_value & 1) == 1) 94 | cond.Wait(lock); 95 | 96 | shared_value++; 97 | cond.NotifyOne(); // Notify the other thread. 98 | } 99 | 100 | thread.Join(); 101 | TEST_TRUE(shared_value == 11); 102 | }; 103 | -------------------------------------------------------------------------------- /Bedrock/StringView.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | uint64 gHash(StringView inValue) 8 | { 9 | return gHash(inValue.Begin(), inValue.Size()); 10 | } 11 | 12 | 13 | REGISTER_TEST("StringView") 14 | { 15 | StringView test = "testtest"; 16 | 17 | TEST_TRUE(test.Size() == 8); 18 | 19 | TEST_TRUE(test.Find('e') == 1); 20 | TEST_TRUE(test.Find('z') == -1); 21 | TEST_TRUE(test.Find('t', 0) == 0); 22 | TEST_TRUE(test.Find('t', 1) == 3); 23 | 24 | TEST_TRUE(test.Find("test") == 0); 25 | TEST_TRUE(test.Find("test", 1) == 4); 26 | TEST_TRUE(test.Find("st") == 2); 27 | TEST_TRUE(test.Find("") == -1); 28 | TEST_TRUE(test.Find("ests") == -1); 29 | TEST_TRUE(test.Contains("st")); 30 | TEST_FALSE(test.Contains("ests")); 31 | 32 | TEST_TRUE(test.FindFirstOf("t") == 0); 33 | TEST_TRUE(test.FindFirstOf("es") == 1); 34 | TEST_TRUE(test.FindFirstOf("zxcv") == -1); 35 | 36 | TEST_TRUE(test.FindLastOf("t") == 7); 37 | TEST_TRUE(test.FindLastOf("es") == 6); 38 | TEST_TRUE(test.FindLastOf("zxcv") == -1); 39 | 40 | TEST_TRUE(test.FindFirstNotOf("t") == 1); 41 | TEST_TRUE(test.FindFirstNotOf("es") == 0); 42 | TEST_TRUE(test.FindFirstNotOf("zxcv") == 0); 43 | TEST_TRUE(test.FindFirstNotOf("tes") == -1); 44 | 45 | TEST_TRUE(test.FindLastNotOf("t") == 6); 46 | TEST_TRUE(test.FindLastNotOf("es") == 7); 47 | TEST_TRUE(test.FindLastNotOf("zxcv") == 7); 48 | TEST_TRUE(test.FindLastNotOf("tes") == -1); 49 | 50 | TEST_TRUE(test.SubStr(1) == "esttest"); 51 | TEST_TRUE(test.SubStr(2, 1) == "s"); 52 | TEST_TRUE(test.SubStr(6, 5) == "st"); 53 | TEST_TRUE(test.SubStr(6, -1) == "st"); // Negative count behaves like cMaxInt 54 | TEST_TRUE(test.SubStr(6, -5) == "st"); 55 | 56 | 57 | StringView empty; 58 | 59 | TEST_TRUE(test.StartsWith("tes")); 60 | TEST_TRUE(test.StartsWith("test")); 61 | TEST_TRUE(test.StartsWith("")); 62 | TEST_TRUE(empty.StartsWith("")); 63 | TEST_FALSE(test.StartsWith("x")); 64 | TEST_FALSE(test.StartsWith("test it")); 65 | TEST_FALSE(empty.StartsWith("test")); 66 | 67 | TEST_TRUE(test.EndsWith("est")); 68 | TEST_TRUE(test.EndsWith("test")); 69 | TEST_TRUE(test.EndsWith("")); 70 | TEST_TRUE(empty.EndsWith("")); 71 | TEST_FALSE(test.EndsWith("x")); 72 | TEST_FALSE(test.EndsWith("test it")); 73 | TEST_FALSE(empty.EndsWith("test")); 74 | 75 | test.RemoveSuffix(2); 76 | TEST_TRUE(test == "testte"); 77 | 78 | test.RemovePrefix(2); 79 | TEST_TRUE(test == "stte"); 80 | 81 | test.RemovePrefix(4); 82 | TEST_TRUE(test == empty); 83 | 84 | { 85 | StringView cmp = "abcd"; 86 | TEST_TRUE(cmp < "bacd"); 87 | TEST_TRUE(cmp < "abcde"); 88 | TEST_FALSE(cmp < "abc"); 89 | TEST_FALSE(cmp < "abad"); 90 | TEST_FALSE(cmp < ""); 91 | TEST_FALSE(StringView("") < ""); 92 | } 93 | }; 94 | 95 | -------------------------------------------------------------------------------- /Bedrock/StringFormat.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #define STB_SPRINTF_IMPLEMENTATION 9 | #include 10 | 11 | #ifdef __SANITIZE_ADDRESS__ 12 | #include // For vsnprintf 13 | #endif 14 | 15 | 16 | void Details::StringFormatV(StringFormatCallback inAppendCallback, void* outString, const char* inFormat, va_list inArgs) 17 | { 18 | // stb_sprintf triggers needs to turn off ASAN around some of its functions, doesn't do it for MSVC's ASAN. 19 | // There is a PR pending to fix this but it's been waiting for years... https://github.com/nothings/stb/pull/1350 20 | // In the meanting, don't use stb_printf when ASAN is enabled, that's the easiest/most reliable workaround. 21 | #ifdef __SANITIZE_ADDRESS__ 22 | // Call vsnprintf a first time to get the string size. 23 | int size = vsnprintf(nullptr, 0, inFormat, inArgs); 24 | if (size <= 0) 25 | return; 26 | 27 | // Allocate a string to store the full string (including null terminator). 28 | // Note: Do not use a TempString here! inAppendCallback might want to grow an existing TempString. 29 | String buffer; 30 | buffer.Reserve(size + 1); 31 | 32 | // Call vsnprintf a second time, with a buffer this time 33 | size = vsnprintf(buffer.Data(), buffer.Capacity(), inFormat, inArgs); 34 | if (size <= 0) 35 | return; 36 | 37 | // Set the correct size and null terminate. 38 | buffer.Resize(size); 39 | 40 | // Call the user callback. 41 | inAppendCallback(buffer.Data(), outString, buffer.Size()); 42 | #else 43 | char buffer[STB_SPRINTF_MIN]; 44 | stbsp_vsprintfcb(inAppendCallback, outString, buffer, inFormat, inArgs); 45 | #endif 46 | } 47 | 48 | 49 | void Details::StringFormat(StringFormatCallback inAppendCallback, void* outString, const char* inFormat, ...) 50 | { 51 | va_list args; 52 | va_start(args, inFormat); 53 | 54 | StringFormatV(inAppendCallback, outString, inFormat, args); 55 | 56 | va_end(args); 57 | } 58 | 59 | 60 | REGISTER_TEST("StringFormat") 61 | { 62 | TEST_INIT_TEMP_MEMORY(1_KiB); 63 | 64 | TempString test = gTempFormat("hello %s %d", "world", 1); 65 | TEST_TRUE(test == "hello world 1"); 66 | 67 | gAppendFormat(test, "%s", "!!"); 68 | TEST_TRUE(test == "hello world 1!!"); 69 | 70 | String test2 = gFormat("also with %s strings", "non-temp"); 71 | TEST_TRUE(test2 == "also with non-temp strings"); 72 | 73 | // TempStrings used in gTempFormat means out of order TempMem frees. Make sure this doesn't assert. 74 | TempString concat = gTempFormat("%s %s %s %s %s %s", 75 | TempString("1").AsCStr(), 76 | TempString("2").AsCStr(), 77 | TempString("3").AsCStr(), 78 | TempString("4").AsCStr(), 79 | TempString("5").AsCStr(), 80 | TempString("6").AsCStr()); 81 | TEST_TRUE(concat == "1 2 3 4 5 6"); 82 | }; -------------------------------------------------------------------------------- /Bedrock/Test.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | struct Test 11 | { 12 | const char* mName; 13 | TestFunction mFunction = nullptr; 14 | }; 15 | 16 | static Vector& sGetAllTests() 17 | { 18 | // Return a static variable to avoid issues with globals initialization order. 19 | static Vector sAllTests; 20 | return sAllTests; 21 | } 22 | 23 | 24 | void gRegisterTest(const char* inName, TestFunction inFunction) 25 | { 26 | gAssert(inName != nullptr && inName[0] != 0); 27 | 28 | sGetAllTests().PushBack({ inName, inFunction }); 29 | } 30 | 31 | 32 | static thread_local StringView sCurrentTestName; 33 | static thread_local bool sCurrentTestSuccess; 34 | 35 | // Extremely basic leak tracking. 36 | static thread_local int sCurrentTestAllocCount; 37 | static thread_local int64 sCurrentTestAllocTotalSize; 38 | 39 | bool gIsRunningTest() 40 | { 41 | return !sCurrentTestName.Empty(); 42 | } 43 | 44 | 45 | TestResult gRunTests() 46 | { 47 | gTrace("Running all tests."); 48 | bool all_success = true; 49 | 50 | for (const Test& test : sGetAllTests()) 51 | { 52 | sCurrentTestName = test.mName; 53 | sCurrentTestSuccess = true; 54 | 55 | sCurrentTestAllocCount = 0; 56 | sCurrentTestAllocTotalSize = 0; 57 | 58 | gTrace(R"(Test "%s" starting.)", test.mName); 59 | Timer timer; 60 | 61 | test.mFunction(); 62 | 63 | if (sCurrentTestAllocCount != 0 || sCurrentTestAllocTotalSize != 0) 64 | { 65 | gTrace("Memory leaks detected: %d allocations (%lld bytes)", sCurrentTestAllocCount, sCurrentTestAllocTotalSize); 66 | sCurrentTestSuccess = false; 67 | 68 | // If a debugger is attached, break to make sure it's noticed. 69 | if (gIsDebuggerAttached()) 70 | BREAKPOINT; 71 | } 72 | 73 | gTrace(R"(Test "%s" finished: %s (%.2f ms))", 74 | test.mName, 75 | sCurrentTestSuccess ? "Success" : "Failure", 76 | gTicksToMilliseconds(timer.GetTicks())); 77 | 78 | all_success = all_success && sCurrentTestSuccess; 79 | sCurrentTestName = ""; 80 | } 81 | 82 | return all_success ? TestResult::Success : TestResult::Failure; 83 | } 84 | 85 | 86 | void gFailTest(const char* inMacro, const char* inCode, const char* inFile, int inLine) 87 | { 88 | gTrace(R"(%s(%s) failed (%s:%d))", inMacro, inCode, inFile, inLine); 89 | 90 | sCurrentTestSuccess = false; 91 | 92 | // If a debugger is attached, break here. 93 | if (gIsDebuggerAttached()) 94 | BREAKPOINT; 95 | } 96 | 97 | 98 | void gRegisterAlloc(MemBlock inMemory) 99 | { 100 | sCurrentTestAllocCount++; 101 | sCurrentTestAllocTotalSize += inMemory.mSize; 102 | } 103 | 104 | void gRegisterFree(MemBlock inMemory) 105 | { 106 | sCurrentTestAllocCount--; 107 | sCurrentTestAllocTotalSize -= inMemory.mSize; 108 | } 109 | -------------------------------------------------------------------------------- /Bedrock/Function.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | template struct Function; 9 | 10 | 11 | // Polymorphic function wrapper. Roughly equivalent to std::function. 12 | template 13 | struct Function 14 | { 15 | Function() = default; 16 | 17 | ~Function() 18 | { 19 | if (IsValid()) 20 | Destruct(); 21 | } 22 | 23 | // No copy for now. 24 | Function(const Function&) = delete; 25 | Function& operator=(const Function&) = delete; 26 | 27 | template 28 | requires (!cIsSame>, Function>) // Don't accept Function types, this is not a copy-constructor. 29 | Function(taFunc&& ioFunc) 30 | { 31 | Construct(gForward(ioFunc)); 32 | } 33 | 34 | Function(Function&& ioOther) 35 | { 36 | if (ioOther.IsValid()) 37 | ioOther.MoveTo(this); 38 | } 39 | 40 | Function& operator=(Function&& ioOther) 41 | { 42 | // Moving from self is not allowed. 43 | gAssert(&mStorage[0] != &ioOther.mStorage[0]); 44 | 45 | if (IsValid()) 46 | Destruct(); 47 | 48 | if (ioOther.IsValid()) 49 | ioOther.MoveTo(this); 50 | 51 | return *this; 52 | } 53 | 54 | bool IsValid() const { return mVTable != nullptr; } 55 | 56 | [[nodiscard]] taResult operator()(taArgs... ioArgs) 57 | { 58 | gAssert(IsValid()); 59 | return mVTable->mInvoke(mStorage, gForward(ioArgs)...); 60 | } 61 | 62 | private: 63 | struct VTable 64 | { 65 | using InvokeFunc = taResult (*)(void*, taArgs&&...); 66 | using DestructFunc = void (*)(void*); 67 | using MoveFunc = void (*)(void*, void*); 68 | 69 | InvokeFunc mInvoke = nullptr; 70 | DestructFunc mDestruct = nullptr; 71 | MoveFunc mMove = nullptr; 72 | }; 73 | 74 | // Contruct from a lambda. 75 | // TODO: add a version for function pointers. 76 | template 77 | void Construct(taLambda&& ioLambda) 78 | { 79 | static_assert(alignof(taLambda) <= cStorageAlignment); 80 | static_assert(sizeof(taLambda) <= cStorageSize); 81 | gAssert(!IsValid()); 82 | 83 | static const VTable sVTable { 84 | [](void* ioStorage, taArgs&&... ioArgs) { return ((taLambda*)ioStorage)->operator()(gForward(ioArgs)...); }, 85 | [](void* ioStorage) { ((taLambda*)ioStorage)->~taLambda(); }, 86 | [](void* ioFrom, void* ioTo) { gPlacementNew(*(taLambda*)ioTo, gMove(*(taLambda*)ioFrom)); }, 87 | }; 88 | 89 | mVTable = &sVTable; 90 | gPlacementNew(*(taLambda*)mStorage, gForward(ioLambda)); 91 | } 92 | 93 | void Destruct() 94 | { 95 | gAssert(IsValid()); 96 | 97 | mVTable->mDestruct(mStorage); 98 | mVTable = nullptr; 99 | } 100 | 101 | void MoveTo(Function* ioTo) 102 | { 103 | gAssert(IsValid()); 104 | gAssert(!ioTo->IsValid()); 105 | 106 | mVTable->mMove(mStorage, ioTo->mStorage); 107 | ioTo->mVTable = mVTable; 108 | 109 | Destruct(); 110 | } 111 | 112 | static constexpr int cStorageSize = sizeof(void*) * 4; 113 | static constexpr int cStorageAlignment = alignof(void*); 114 | 115 | const VTable* mVTable = nullptr; 116 | alignas(cStorageAlignment) uint8 mStorage[cStorageSize]; 117 | }; -------------------------------------------------------------------------------- /Bedrock/Atomic.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | template 8 | static void sTestAtomic(Atomic& ioAtomic, MemoryOrder inMemoryOrder, auto inA, auto inB) 9 | { 10 | ioAtomic.Store(inA, inMemoryOrder); 11 | TEST_TRUE(ioAtomic.Load(inMemoryOrder) == inA); 12 | 13 | ioAtomic.Store(inB, inMemoryOrder); 14 | TEST_TRUE(ioAtomic.Load(inMemoryOrder) == inB); 15 | 16 | TEST_TRUE(ioAtomic.Exchange(inA, inMemoryOrder) == inB); 17 | 18 | if constexpr (cIsIntegral && !cIsSame) 19 | { 20 | gAssert(inA < inB); // Min/max below rely on this 21 | 22 | TEST_TRUE(ioAtomic.Add(inB, inMemoryOrder) == inA); 23 | TEST_TRUE(ioAtomic.Load(inMemoryOrder) == inA + inB); 24 | TEST_TRUE(ioAtomic.Sub(inA, inMemoryOrder) == inA + inB); 25 | TEST_TRUE(ioAtomic.Load(inMemoryOrder) == inB); 26 | 27 | ioAtomic.Store(inA); 28 | TEST_TRUE(ioAtomic.Max(inB, inMemoryOrder) == inA); 29 | TEST_TRUE(ioAtomic.Load() == inB); 30 | TEST_TRUE(ioAtomic.Max(inA, inMemoryOrder) == inB); 31 | TEST_TRUE(ioAtomic.Load() == inB); 32 | 33 | ioAtomic.Store(inB); 34 | TEST_TRUE(ioAtomic.Min(inA, inMemoryOrder) == inB); 35 | TEST_TRUE(ioAtomic.Load() == inA); 36 | TEST_TRUE(ioAtomic.Min(inB, inMemoryOrder) == inA); 37 | TEST_TRUE(ioAtomic.Load() == inA); 38 | } 39 | 40 | // Exchange success 41 | ioAtomic.Store(inB); 42 | taType expected = inB; 43 | TEST_TRUE(ioAtomic.CompareExchange(expected, inA)); 44 | TEST_TRUE(ioAtomic.Load() == inA); 45 | TEST_TRUE(expected == inB); 46 | 47 | // Exchange failure 48 | TEST_FALSE(ioAtomic.CompareExchange(expected, inB)); 49 | TEST_TRUE(ioAtomic.Load() == inA); 50 | TEST_TRUE(expected == inA); 51 | } 52 | 53 | 54 | REGISTER_TEST("AtomicInt32") 55 | { 56 | AtomicInt32 atomic; 57 | 58 | sTestAtomic(atomic, MemoryOrder::Relaxed, 1, 9999); 59 | sTestAtomic(atomic, MemoryOrder::SeqCst, 1, 9999); 60 | }; 61 | 62 | 63 | REGISTER_TEST("AtomicInt8") 64 | { 65 | AtomicInt8 atomic; 66 | 67 | sTestAtomic(atomic, MemoryOrder::Relaxed, 20, 42); 68 | sTestAtomic(atomic, MemoryOrder::SeqCst, 20, 42); 69 | }; 70 | 71 | 72 | REGISTER_TEST("AtomicInt64") 73 | { 74 | AtomicInt64 atomic; 75 | 76 | sTestAtomic(atomic, MemoryOrder::Relaxed, 1000, (int64)cMaxInt * 10); 77 | sTestAtomic(atomic, MemoryOrder::SeqCst, 1000, (int64)cMaxInt * 10); 78 | }; 79 | 80 | 81 | REGISTER_TEST("AtomicBool") 82 | { 83 | AtomicBool atomic; 84 | 85 | sTestAtomic(atomic, MemoryOrder::Relaxed, false, true); 86 | sTestAtomic(atomic, MemoryOrder::SeqCst, false, true); 87 | }; 88 | 89 | 90 | REGISTER_TEST("AtomicObject") 91 | { 92 | struct Test 93 | { 94 | int mValue; 95 | 96 | bool operator==(const Test&) const = default; 97 | }; 98 | Atomic atomic; 99 | 100 | sTestAtomic(atomic, MemoryOrder::Relaxed, Test{ 100 }, Test{ 5000 }); 101 | sTestAtomic(atomic, MemoryOrder::SeqCst, Test{ 100 }, Test{ 5000 }); 102 | }; 103 | 104 | 105 | REGISTER_TEST("AtomicPtr") 106 | { 107 | int test; 108 | Atomic atomic; 109 | 110 | sTestAtomic(atomic, MemoryOrder::Relaxed, &test, &test + 100); 111 | sTestAtomic(atomic, MemoryOrder::SeqCst, &test, &test + 100); 112 | }; 113 | 114 | 115 | REGISTER_TEST("AtomicEnum") 116 | { 117 | enum class TestEnum : int { A, B }; 118 | Atomic atomic; 119 | 120 | sTestAtomic(atomic, MemoryOrder::Relaxed, TestEnum::A, TestEnum::B); 121 | sTestAtomic(atomic, MemoryOrder::SeqCst, TestEnum::A, TestEnum::B); 122 | }; 123 | -------------------------------------------------------------------------------- /Bedrock/Thread.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define VC_EXTRALEAN 8 | #define WIN32_LEAN_AND_MEAN 9 | #define NOMINMAX 10 | #include 11 | 12 | struct ThreadInternal 13 | { 14 | static DWORD WINAPI sThreadProc(LPVOID inParam) 15 | { 16 | Thread& thread = *(Thread*)inParam; 17 | 18 | // Set the thread name. 19 | if (!thread.mConfig.mName.Empty()) 20 | gSetCurrentThreadName(thread.mConfig.mName.AsCStr()); 21 | 22 | // Allocate temp memory. 23 | if (thread.mConfig.mTempMemSize > 0) 24 | gThreadInitTempMemory(gMemAlloc(thread.mConfig.mTempMemSize)); 25 | 26 | // Call the entry point. 27 | thread.mEntryPoint(thread); 28 | 29 | // Free temp memory. 30 | if (thread.mConfig.mTempMemSize > 0) 31 | gMemFree(gThreadExitTempMemory()); 32 | 33 | return 0; 34 | } 35 | }; 36 | 37 | 38 | Thread::~Thread() 39 | { 40 | Join(); 41 | } 42 | 43 | 44 | void Thread::Create(const ThreadConfig& inConfig, Function&& ioEntryPoint) 45 | { 46 | gAssert(mOSThread == nullptr); // There's already a thread running! 47 | 48 | mEntryPoint = gMove(ioEntryPoint); 49 | mConfig = inConfig; 50 | 51 | // Create the thread in a suspended state. 52 | mOSThread = CreateThread(nullptr, inConfig.mStackSize, ThreadInternal::sThreadProc, this, CREATE_SUSPENDED, &mOSThreadID); 53 | gAssert(mOSThread != nullptr); 54 | 55 | // Set the priority. 56 | int priority = THREAD_PRIORITY_NORMAL; 57 | switch (inConfig.mPriority) 58 | { 59 | case EThreadPriority::Idle: priority = THREAD_PRIORITY_IDLE; break; 60 | case EThreadPriority::Lowest: priority = THREAD_PRIORITY_LOWEST; break; 61 | case EThreadPriority::BelowNormal: priority = THREAD_PRIORITY_BELOW_NORMAL; break; 62 | case EThreadPriority::Normal: priority = THREAD_PRIORITY_NORMAL; break; 63 | case EThreadPriority::AboveNormal: priority = THREAD_PRIORITY_ABOVE_NORMAL; break; 64 | case EThreadPriority::Highest: priority = THREAD_PRIORITY_HIGHEST; break; 65 | } 66 | SetThreadPriority(mOSThread, priority); 67 | 68 | // Start the thread. 69 | ResumeThread(mOSThread); 70 | } 71 | 72 | 73 | void Thread::Join() 74 | { 75 | if (mOSThread == nullptr) 76 | return; 77 | 78 | // Wait for it to stop. 79 | WaitForSingleObject(mOSThread, INFINITE); 80 | CloseHandle(mOSThread); 81 | 82 | Cleanup(); 83 | } 84 | 85 | 86 | // Reset everything to default/empty. 87 | void Thread::Cleanup() 88 | { 89 | mEntryPoint = {}; 90 | mConfig = {}; 91 | mOSThread = {}; 92 | mOSThreadID = 0; 93 | mStopRequested.Store(false); 94 | } 95 | 96 | 97 | // Number of threads that can run concurrently. 98 | // Equivalent to the number of CPU cores (incuding hyperthreading logical cores). 99 | int gThreadHardwareConcurrency() 100 | { 101 | static const int number_of_cpus = [] { 102 | SYSTEM_INFO system_info = {}; 103 | GetSystemInfo(&system_info); 104 | return gMax(1, (int)system_info.dwNumberOfProcessors); 105 | }(); 106 | 107 | return number_of_cpus; 108 | } 109 | 110 | 111 | // Yield the processor to other threads that are ready to run. 112 | void gYieldThread() 113 | { 114 | SwitchToThread(); 115 | } 116 | 117 | 118 | REGISTER_TEST("Thread") 119 | { 120 | Thread thread; 121 | 122 | bool set_by_thread = false; 123 | 124 | thread.Create( 125 | { 126 | .mName = "TestThread", 127 | .mTempMemSize = 10_KiB, 128 | }, 129 | [&set_by_thread](Thread& ioSelf) 130 | { 131 | TEST_TRUE(gTempMemArena.GetMemBlock().mSize == 10_KiB); 132 | 133 | set_by_thread = true; 134 | 135 | while (!ioSelf.IsStopRequested()) 136 | gYieldThread(); 137 | }); 138 | 139 | TEST_TRUE(thread.IsStopRequested() == false); 140 | thread.RequestStop(); 141 | TEST_TRUE(thread.IsStopRequested() == true); 142 | 143 | thread.Join(); 144 | 145 | TEST_TRUE(set_by_thread); 146 | 147 | TEST_TRUE(gThreadHardwareConcurrency() >= 1); 148 | }; -------------------------------------------------------------------------------- /Bedrock/StringFormat.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | 8 | #ifdef __clang__ 9 | typedef __builtin_va_list va_list; 10 | #elif _MSC_VER 11 | typedef char* va_list; 12 | #else 13 | #error Unknown compiler 14 | #endif 15 | 16 | 17 | // Format a String and return it. 18 | // Note: The fake call to printf is there to catch format errors. Unlike attribute format, this also works with MSVC. 19 | #define gFormat(format, ...) \ 20 | ((void)sizeof(printf(format, __VA_ARGS__)), Details::StringFormatRet(format, __VA_ARGS__)) 21 | 22 | 23 | // Format a TempString and return it. 24 | // Note: The fake call to printf is there to catch format errors. Unlike attribute format, this also works with MSVC. 25 | #define gTempFormat(format, ...) \ 26 | ((void)sizeof(printf(format, __VA_ARGS__)), Details::StringFormatRet(format, __VA_ARGS__)) 27 | 28 | 29 | // Format and append into @a output. 30 | // Output can be any String-like class, it only needs an Append(const char*, int) method. 31 | // Note: The fake call to printf is there to catch format errors. Unlike attribute format, this also works with MSVC. 32 | #define gAppendFormat(output, format, ...) \ 33 | do \ 34 | { \ 35 | (void)sizeof(printf(format, __VA_ARGS__)); \ 36 | Details::StringFormat(output, format, __VA_ARGS__); \ 37 | } while (false) 38 | 39 | 40 | 41 | namespace Details 42 | { 43 | // Callback passed to stbsp_vsprintfcb. 44 | template 45 | char* StringFormatAppendCallback(const char* inBuffer, void* outString, int inBufferLength) 46 | { 47 | taString& output = *(taString*)outString; 48 | output.Append(inBuffer, inBufferLength); 49 | 50 | // Reuse the same buffer. 51 | return const_cast(inBuffer); 52 | } 53 | 54 | using StringFormatCallback = char*(*)(const char* inBuffer, void* outString, int inBufferLength); 55 | 56 | // Internal functions doing the actual formatting. 57 | void StringFormat(StringFormatCallback inAppendCallback, void* outString, const char* inFormat, ...); 58 | void StringFormatV(StringFormatCallback inAppendCallback, void* outString, const char* inFormat, va_list inArgs); 59 | 60 | // Template function helper to automatically get the right callback. 61 | template 62 | void StringFormat(taString& outString, const char* inFormat, taArgs&&... ioArgs) 63 | { 64 | StringFormat(&StringFormatAppendCallback, &outString, inFormat, gForward(ioArgs)...); 65 | } 66 | 67 | // Template function helper to automatically get the right callback. 68 | template 69 | void StringFormatV(taString& outString, const char* inFormat, va_list inArgs) 70 | { 71 | StringFormatV(&StringFormatAppendCallback, &outString, inFormat, inArgs); 72 | } 73 | 74 | // Template function helper to format a string and return it. 75 | template 76 | taString StringFormatRet(const char* inFormat, taArgs&&... ioArgs) 77 | { 78 | taString out_string; 79 | StringFormat(out_string, inFormat, gForward(ioArgs)...); 80 | return out_string; 81 | } 82 | } 83 | 84 | 85 | // Variant of gFormat that takes a va_list. 86 | inline String gFormatV(const char* inFormat, va_list inArgs) 87 | { 88 | String str; 89 | Details::StringFormatV(str, inFormat, inArgs); 90 | return str; 91 | } 92 | 93 | 94 | // Variant of gTempFormat that takes a va_list. 95 | inline TempString gTempFormatV(const char* inFormat, va_list inArgs) 96 | { 97 | TempString str; 98 | Details::StringFormatV(str, inFormat, inArgs); 99 | return str; 100 | } 101 | 102 | 103 | // Variant of gAppendFormat that takes a va_list. 104 | template 105 | void gAppendFormatV(taString& outString, const char* inFormat, va_list inArgs) 106 | { 107 | Details::StringFormatV(outString, inFormat, inArgs); 108 | } 109 | 110 | 111 | // Printf forward declaration for the format validation hack above. 112 | extern "C" int __cdecl printf(const char* inFormat, ...); 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Bedrock/Test.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // Tests are only enabled in debug. They are compiled but not registered in release (and should be optimized out). 9 | #ifdef ASSERTS_ENABLED 10 | #define TESTS_ENABLED 11 | #endif 12 | 13 | #ifdef TESTS_ENABLED 14 | #define IF_TESTS_ENABLED(code) code 15 | #else 16 | #define IF_TESTS_ENABLED(code) 17 | #endif 18 | 19 | 20 | using TestFunction = void (*)(); 21 | 22 | enum class TestResult 23 | { 24 | Success, 25 | Failure 26 | }; 27 | 28 | 29 | // Register a test. Called automatically by REGISTER_TEST. 30 | void gRegisterTest(const char* inName, TestFunction inFunction); 31 | 32 | // Run all registered tests. 33 | TestResult gRunTests(); 34 | 35 | // Return true if the current thread is running a test. 36 | bool gIsRunningTest(); 37 | 38 | // Fail current test. Called automatically by the TEST macros. 39 | void gFailTest(const char* inMacro, const char* inCode, const char* inFile, int inLine); 40 | 41 | // During tests, memory allocation functions can register their allocations/frees, for leak tracking. 42 | void gRegisterAlloc(MemBlock inMemory); 43 | void gRegisterFree(MemBlock inMemory); 44 | 45 | namespace Details 46 | { 47 | struct TestDummy { 48 | const char* mName; 49 | consteval TestDummy(const char* inName) 50 | { 51 | gAssert(inName != nullptr && inName[0] != 0); 52 | mName = inName; 53 | } 54 | }; 55 | 56 | struct TestRegisterer { TestRegisterer(const char* inName, TestFunction inFunction) { IF_TESTS_ENABLED(gRegisterTest(inName, inFunction)); } }; 57 | inline TestRegisterer operator*(TestDummy inDummy, TestFunction inFunction) { return { inDummy.mName, inFunction }; } 58 | } 59 | 60 | // Register a test. 61 | // eg. 62 | // 63 | // REGISTER_TEST("Examples") 64 | // { 65 | // TEST_TRUE(gGetTheAnswer() == 42); 66 | // TEST_FALSE(gIsTooManyCooks()); 67 | // }; 68 | #define REGISTER_TEST(name) static auto TOKEN_PASTE(test_register, __LINE__) = Details::TestDummy{ name } *[]() 69 | 70 | // Check that a condition is true. Fail the current test otherwise. 71 | #define TEST_TRUE(code) do { if (!(code)) gFailTest("TEST_TRUE", #code, Details::ConstevalGetFileNamePart(__FILE__), __LINE__); } while(0) 72 | 73 | // Check that a condition is false. Fail the current test otherwise. 74 | #define TEST_FALSE(code) do { if (code) gFailTest("TEST_FALSE", #code, Details::ConstevalGetFileNamePart(__FILE__), __LINE__); } while(0) 75 | 76 | 77 | // Initialize Temporary Memory for the scope of a test. 78 | // eg. TEST_INIT_TEMP_MEMORY(100_KiB); 79 | #define TEST_INIT_TEMP_MEMORY(size_in_bytes) Details::TestScopedTempMemory TOKEN_PASTE(test_temp_mem, __LINE__) 80 | 81 | 82 | // Get the file name part of a path. eg. file.cpp for path/to/file.cpp 83 | constexpr const char* gGetFileNamePart(const char* inPath) 84 | { 85 | int after_last_slash = 0; 86 | int i = 0; 87 | while (inPath[i] != 0) 88 | { 89 | if (inPath[i] == '/' || inPath[i] == '\\') 90 | after_last_slash = i + 1; 91 | i++; 92 | } 93 | 94 | return inPath + after_last_slash; 95 | } 96 | 97 | 98 | namespace Details 99 | { 100 | // Same as gGetFileNamePart but consteval 101 | consteval const char* ConstevalGetFileNamePart(const char* inPath) 102 | { 103 | return gGetFileNamePart(inPath); 104 | } 105 | 106 | // Helper to initialize temporary memory for the scope of a test. 107 | template 108 | struct TestScopedTempMemory : NoCopy 109 | { 110 | [[nodiscard]] TestScopedTempMemory() 111 | { 112 | // Save current temp memory setup. 113 | mSavedTempMemArena = gMove(gTempMemArena); 114 | 115 | // (Re-)initialize the temp memory with the internal buffer. 116 | gTempMemArena = {}; 117 | gThreadInitTempMemory({ mBuffer, sizeof(mBuffer) }); 118 | } 119 | 120 | ~TestScopedTempMemory() 121 | { 122 | // Deinitialize the temp memory. This asserts that everything was freed. 123 | MemBlock memory = gThreadExitTempMemory(); 124 | gAssert(memory.mPtr == mBuffer); 125 | gAssert(memory.mSize == sizeof(mBuffer)); 126 | 127 | // Restore the saved temp memory setup. 128 | gTempMemArena = gMove(mSavedTempMemArena); 129 | } 130 | 131 | TempMemArena mSavedTempMemArena; 132 | 133 | alignas(TempMemArena::cAlignment) uint8 mBuffer[taSize]; 134 | }; 135 | } 136 | 137 | -------------------------------------------------------------------------------- /Bedrock/Span.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | template 9 | struct Span 10 | { 11 | using ElementType = taType; 12 | using ValueType = RemoveCV; 13 | 14 | constexpr Span() = default; 15 | constexpr Span(const Span&) = default; 16 | constexpr Span(Span&&) = default; 17 | constexpr ~Span() = default; 18 | constexpr Span& operator=(const Span&) = default; 19 | constexpr Span& operator=(Span&&) = default; 20 | 21 | // Construction from pointers 22 | constexpr Span(taType* inElems, int inSize) : mData(inElems), mSize(inSize) {} 23 | constexpr Span(taType* inBegin, taType* inEnd) : mData(inBegin), mSize((int)(inEnd - inBegin)) { gAssert(inEnd >= inBegin); } 24 | 25 | // Construction from array 26 | template 27 | constexpr Span(taType (&inElems)[taSize]) : mData(inElems), mSize(taSize) {} 28 | 29 | // Construction from Span of a different type (for conversion to const among other things) 30 | template 31 | requires cIsConvertible 32 | constexpr Span(const Span& inOtherSpan) : mData(inOtherSpan.Begin()), mSize(inOtherSpan.Size()) {} 33 | 34 | // Constructor from containers 35 | template 36 | requires cIsContiguous> && cIsConvertible 37 | constexpr Span(taContainer& inContainer) : mData(inContainer.Begin()), mSize(inContainer.Size()) {} 38 | 39 | constexpr int Size() const { return mSize; } 40 | constexpr int SizeInBytes() const { return mSize * sizeof(taType); } 41 | constexpr bool Empty() const { return mSize == 0; } 42 | 43 | constexpr const taType* Data() const { return mData; } 44 | constexpr taType* Data() { return mData; } 45 | 46 | constexpr const taType* Begin() const { return mData; } 47 | constexpr const taType* End() const { return mData + mSize; } 48 | constexpr const taType* begin() const { return mData; } 49 | constexpr const taType* end() const { return mData + mSize; } 50 | constexpr taType* Begin() { return mData; } 51 | constexpr taType* End() { return mData + mSize; } 52 | constexpr taType* begin() { return mData; } 53 | constexpr taType* end() { return mData + mSize; } 54 | 55 | constexpr const taType& Front() const { gAssert(mSize > 0); return mData[0]; } 56 | constexpr const taType& Back() const { gAssert(mSize > 0); return mData[mSize - 1]; } 57 | constexpr taType& Front() { gAssert(mSize > 0); return mData[0]; } 58 | constexpr taType& Back() { gAssert(mSize > 0); return mData[mSize - 1]; } 59 | constexpr int GetIndex(const taType& inElement) const; 60 | 61 | constexpr const taType& operator[](int inPosition) const { gBoundsCheck(inPosition, mSize); return mData[inPosition]; } 62 | constexpr taType& operator[](int inPosition) { gBoundsCheck(inPosition, mSize); return mData[inPosition]; } 63 | 64 | constexpr bool operator==(Span inOther) const { return gEquals(*this, inOther); } 65 | template 66 | requires cIsConvertible 67 | constexpr bool operator==(Span inOther) const { return gEquals(*this, inOther); } 68 | 69 | constexpr Span First(int inCount) const { gBoundsCheck(inCount, mSize); return { mData, inCount }; } 70 | constexpr Span Last(int inCount) const { gBoundsCheck(inCount, mSize); return { mData + mSize - inCount, inCount }; } 71 | constexpr Span SubSpan(int inPosition, int inCount = cMaxInt) const; // Note: negative inCount behaves like cMaxInt 72 | 73 | private: 74 | taType* mData = nullptr; 75 | int mSize = 0; 76 | }; 77 | 78 | 79 | // Span is a contiguous container. 80 | template inline constexpr bool cIsContiguous> = true; 81 | 82 | 83 | // Deduction guides. 84 | template 85 | Span(taContainer) -> Span; 86 | 87 | 88 | template 89 | constexpr int Span::GetIndex(const taType& inElement) const 90 | { 91 | int index = (int)(&inElement - mData); 92 | gBoundsCheck(index, mSize); 93 | return index; 94 | } 95 | 96 | 97 | template 98 | constexpr Span Span::SubSpan(int inPosition, int inCount) const 99 | { 100 | gBoundsCheck(inPosition, mSize + 1); // Note: inPosition == mSize is allowed. 101 | 102 | if (inCount < 0) 103 | inCount = cMaxInt; 104 | 105 | int size = gMin(inCount, mSize - inPosition); 106 | return { mData + inPosition, size }; 107 | } 108 | -------------------------------------------------------------------------------- /Bedrock/String.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | 5 | 6 | REGISTER_TEST("String") 7 | { 8 | { 9 | String test = "test"; 10 | 11 | TEST_TRUE(test.Size() == 4); 12 | TEST_TRUE(test.Capacity() >= 5); 13 | TEST_TRUE(test == "test"); 14 | TEST_TRUE(*test.End() == 0); 15 | 16 | char* test_begin = test.Begin(); 17 | test.Reserve(30); 18 | TEST_TRUE(test.Begin() != test_begin); // Should have re-allocated. 19 | TEST_TRUE(test.Capacity() >= 30); 20 | TEST_TRUE(test.Size() == 4); 21 | TEST_TRUE(*test.End() == 0); 22 | 23 | test.Resize(2); 24 | TEST_TRUE(test == "te"); 25 | TEST_TRUE(test.Capacity() >= 30); 26 | TEST_TRUE(test.Size() == 2); 27 | TEST_TRUE(*test.End() == 0); 28 | 29 | test = {}; 30 | TEST_TRUE(test.Capacity() >= 30); // Move-assigning an empty string should not free memory. 31 | TEST_TRUE(test.Size() == 0); 32 | 33 | test = "other_test"; 34 | StringView other_test = test; 35 | TEST_TRUE(test == other_test); 36 | 37 | StringView test_as_sv = test; 38 | TEST_TRUE(test == test_as_sv); 39 | TEST_TRUE(test.Begin() == test_as_sv.Begin()); 40 | 41 | test_begin = test.Begin(); 42 | String moved_test = gMove(test); 43 | TEST_TRUE(moved_test.Begin() == test_begin); 44 | TEST_TRUE(test.Begin() == StringView().Begin()); 45 | 46 | test = "test"; 47 | String test2 = "other test"; 48 | test2 = gMove(test); // test2 should free its previous alloc. Leak detection will tell. 49 | TEST_TRUE(test.Empty()); 50 | 51 | struct TestStringView : StringView 52 | { 53 | using StringView::cEmpty; // Making this public for tests 54 | }; 55 | 56 | // Empty strings all point to the same 1 byte buffer (cEmpty) to avoid allocations. 57 | String empty; 58 | TEST_TRUE(empty.AsCStr() == TestStringView::cEmpty); 59 | empty = ""; 60 | TEST_TRUE(empty.AsCStr() == TestStringView::cEmpty); 61 | String empty2 = empty; 62 | TEST_TRUE(empty2.AsCStr() == TestStringView::cEmpty); 63 | test = "other_test"; 64 | test = empty; // Copy but keep existing alloc. 65 | TEST_TRUE(test.Empty()); 66 | TEST_TRUE(test.AsCStr() != TestStringView::cEmpty); 67 | test.Append({}); 68 | TEST_TRUE(test.Empty()); 69 | empty.Append({}); 70 | TEST_TRUE(empty.AsCStr() == TestStringView::cEmpty); 71 | empty = gMove(test); // Pass alloc to empty. 72 | TEST_TRUE(empty.AsCStr() != TestStringView::cEmpty); 73 | TEST_TRUE(test.AsCStr() == TestStringView::cEmpty); 74 | 75 | test.Clear(); 76 | test.Append("test"); 77 | TEST_TRUE(test == "test"); 78 | test.Append("test2"); 79 | TEST_TRUE(test == "testtest2"); 80 | 81 | test.RemoveSuffix(4); 82 | TEST_TRUE(test == "testt"); 83 | TEST_TRUE(*test.End() == 0); 84 | 85 | TEST_TRUE(test.Capacity() > test.Size()); 86 | int cap = test.Capacity(); 87 | test.ShrinkToFit(); 88 | TEST_TRUE(test.Capacity() == cap); // ShrinkToFit doesn't do anything with a heap allocator (doesn't support TryReallocate) 89 | } 90 | 91 | // Insert 92 | { 93 | String test = "123456789"; 94 | 95 | for (int i = 3; i < test.Size(); i += 4) 96 | test.Insert(i, "'"); 97 | TEST_TRUE(test == "123'456'789"); 98 | 99 | test.Insert(test.Size(), "!!!"); 100 | TEST_TRUE(test == "123'456'789!!!"); 101 | 102 | test.Insert(0, "a big number is "); 103 | TEST_TRUE(test == "a big number is 123'456'789!!!"); 104 | } 105 | }; 106 | 107 | 108 | REGISTER_TEST("TempString") 109 | { 110 | // Make sure temp memory is initialized or the tests will fail. 111 | TEST_INIT_TEMP_MEMORY(1_KiB); 112 | 113 | TempString test = "test"; 114 | 115 | TEST_TRUE(gTempMemArena.Owns(test.Begin())); 116 | TEST_TRUE(test.Size() == 4); 117 | TEST_TRUE(test.Capacity() >= 5); 118 | TEST_TRUE(test == "test"); 119 | TEST_TRUE(*test.End() == 0); 120 | 121 | char* test_begin = test.Begin(); 122 | test.Reserve(30); 123 | TEST_TRUE(test.Begin() == test_begin); // Should have resized the memory block. 124 | TEST_TRUE(test.Capacity() >= 30); 125 | TEST_TRUE(test.Size() == 4); 126 | TEST_TRUE(*test.End() == 0); 127 | 128 | String non_temp = test; 129 | TEST_TRUE(test.Begin() != non_temp.Begin()); 130 | TEST_TRUE(test == non_temp); 131 | 132 | non_temp = "other test"; 133 | test = non_temp; 134 | TEST_TRUE(test.Begin() == test_begin); 135 | TEST_TRUE(test.Begin() != non_temp.Begin()); 136 | TEST_TRUE(test == non_temp); 137 | 138 | test.Append("add"); 139 | test.RemoveSuffix(3); 140 | TEST_TRUE(test.Capacity() > test.Size()); 141 | test.ShrinkToFit(); 142 | TEST_TRUE(test.Capacity() == test.Size() + 1); 143 | }; 144 | 145 | 146 | 147 | REGISTER_TEST("FixedString") 148 | { 149 | FixedString<32> test = "test"; 150 | 151 | TEST_TRUE(test.Size() == 4); 152 | TEST_TRUE(test.Capacity() >= 5); 153 | TEST_TRUE(test == "test"); 154 | TEST_TRUE(*test.End() == 0); 155 | TEST_TRUE(test.MaxSize() == 31); // One extra char for the null terminator 156 | 157 | char* test_begin = test.Begin(); 158 | test.Reserve(30); 159 | TEST_TRUE(test.Begin() == test_begin); // Should have resized the memory block. 160 | TEST_TRUE(test.Capacity() >= 30); 161 | TEST_TRUE(test.Size() == 4); 162 | TEST_TRUE(*test.End() == 0); 163 | 164 | String non_temp = test; 165 | TEST_TRUE(test.Begin() != non_temp.Begin()); 166 | TEST_TRUE(test == non_temp); 167 | 168 | non_temp = "other test"; 169 | test = non_temp; 170 | TEST_TRUE(test.Begin() == test_begin); 171 | TEST_TRUE(test.Begin() != non_temp.Begin()); 172 | TEST_TRUE(test == non_temp); 173 | 174 | test.Append("add"); 175 | test.RemoveSuffix(3); 176 | TEST_TRUE(test.Capacity() > test.Size()); 177 | test.ShrinkToFit(); 178 | TEST_TRUE(test.Capacity() == test.Size() + 1); 179 | }; 180 | -------------------------------------------------------------------------------- /Bedrock/TypeTraits.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | 6 | // Equivalent to std::is_class 7 | template constexpr bool cIsClass = __is_class(T); 8 | 9 | // Equivalent to std::is_enum 10 | template constexpr bool cIsEnum = __is_enum(T); 11 | 12 | // Equivalent to std::is_union 13 | template constexpr bool cIsUnion = __is_union(T); 14 | 15 | // Equivalent to std::is_move_constructible 16 | template constexpr bool cIsMoveConstructible = __is_constructible(T, T); 17 | 18 | // Equivalent to std::is_move_assignable 19 | template constexpr bool cIsMoveAssignable = __is_assignable(T&, T&&); 20 | 21 | // Equivalent to std::is_assignable 22 | template constexpr bool cIsAssignable = __is_assignable(T, U); 23 | 24 | // Equivalent to std::is_convertible 25 | template constexpr bool cIsConvertible = __is_convertible_to(taFrom, taTo); 26 | 27 | // Equivalent to std::has_unique_object_representations 28 | template constexpr bool cHasUniqueObjectRepresentations = __has_unique_object_representations(T); 29 | 30 | // Equivalent to std::is_trivially_default_constructible 31 | template constexpr bool cIsTriviallyDefaultConstructible = __is_trivially_constructible(T); 32 | 33 | // Equivalent to std::is_trivially_copyable 34 | template constexpr bool cIsTriviallyCopyable = __is_trivially_copyable(T); 35 | 36 | // Equivalent to std::is_const 37 | namespace Details 38 | { 39 | template struct IsConst { static constexpr bool cValue = false; }; 40 | template struct IsConst { static constexpr bool cValue = true; }; 41 | } 42 | template constexpr bool cIsConst = Details::IsConst::cValue; 43 | 44 | // Equivalent to std::is_same 45 | namespace Details 46 | { 47 | template struct IsSame { static constexpr bool cValue = false; }; 48 | template struct IsSame { static constexpr bool cValue = true; }; 49 | } 50 | template constexpr bool cIsSame = Details::IsSame::cValue; 51 | 52 | // True if T is any of the types in taTypes 53 | template constexpr bool cIsAnyOf = (cIsSame || ...); 54 | 55 | // Equivalent to std::remove_reference 56 | namespace Details 57 | { 58 | template struct RemoveReference { using Type = T; }; 59 | template struct RemoveReference { using Type = T; }; 60 | template struct RemoveReference { using Type = T; }; 61 | } 62 | template using RemoveReference = typename Details::RemoveReference::Type; 63 | 64 | // Equivalent to std::remove_cv 65 | namespace Details 66 | { 67 | template struct RemoveCV { using Type = T; }; 68 | template struct RemoveCV { using Type = T; }; 69 | template struct RemoveCV { using Type = T; }; 70 | template struct RemoveCV { using Type = T; }; 71 | } 72 | template using RemoveCV = typename Details::RemoveCV::Type; 73 | 74 | // Equivalent to std::remove_pointer 75 | namespace Details 76 | { 77 | template struct RemovePointer { using Type = T; }; 78 | template struct RemovePointer { using Type = T; }; 79 | template struct RemovePointer { using Type = T; }; 80 | template struct RemovePointer { using Type = T; }; 81 | template struct RemovePointer { using Type = T; }; 82 | } 83 | template using RemovePointer = typename Details::RemovePointer::Type; 84 | 85 | // Equivalent to std::is_lvalue_reference 86 | namespace Details 87 | { 88 | template struct IsLValueReference { static constexpr bool cValue = false; }; 89 | template struct IsLValueReference { static constexpr bool cValue = true; }; 90 | } 91 | template constexpr bool cIsLValueReference = Details::IsConst::cValue; 92 | 93 | // Equivalent to std::is_void 94 | template constexpr bool cIsVoid = cIsSame>; 95 | 96 | // Equivalent to std::is_pointer 97 | namespace Details 98 | { 99 | template struct IsPointer { static constexpr bool cValue = false; }; 100 | template struct IsPointer { static constexpr bool cValue = true; }; 101 | template struct IsPointer { static constexpr bool cValue = true; }; 102 | template struct IsPointer { static constexpr bool cValue = true; }; 103 | template struct IsPointer { static constexpr bool cValue = true; }; 104 | } 105 | template constexpr bool cIsPointer = Details::IsPointer::cValue; 106 | 107 | // Equivalent to std::conditional 108 | namespace Details 109 | { 110 | template struct Conditional { using Type = T; }; 111 | template struct Conditional { using Type = F; }; 112 | } 113 | template using Conditional = typename Details::Conditional::Type; 114 | 115 | // True if the elements of a container are contiguous in memory. 116 | // Each contiguous container needs to add its specialization. 117 | template constexpr bool cIsContiguous = false; 118 | 119 | // True if the elements of a container stay at the same address when it grows. 120 | // Each contiguous container needs to add its specialization. 121 | template constexpr bool cIsStable = false; 122 | 123 | // Equivalent to std::underlying_type 124 | template requires cIsEnum using UnderlyingType = __underlying_type(T); 125 | 126 | // Equivalent to std::to_underlying 127 | template requires cIsEnum 128 | [[nodiscard]] ATTRIBUTE_INTRINSIC constexpr auto gToUnderlying(taEnum inEnum) { return static_cast>(inEnum); } 129 | 130 | // Equivalent to std::is_integral 131 | template constexpr bool cIsIntegral = cIsAnyOf, bool, char, signed char, unsigned char, short, unsigned short, int, unsigned int, long, unsigned long, long long, unsigned long long>; 132 | 133 | // Equivalent to std::integral 134 | template concept Integral = cIsIntegral; 135 | 136 | // Equivalent to std::as_const 137 | template 138 | [[nodiscard]] ATTRIBUTE_INTRINSIC constexpr const T& gAsConst(T& inValue) { return inValue; } 139 | -------------------------------------------------------------------------------- /Bedrock/Algorithm.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | 8 | // Equivalent to std::swap 9 | template 10 | constexpr void gSwap(taType& ioA, taType& ioB) 11 | { 12 | taType temp = gMove(ioA); 13 | ioA = gMove(ioB); 14 | ioB = gMove(temp); 15 | } 16 | 17 | 18 | // Forward declaration of the Hash struct. 19 | // Equivalent to std::hash. 20 | template struct Hash; 21 | 22 | 23 | // Minimal reverse iterator. 24 | template 25 | struct ReverseIterator 26 | { 27 | constexpr ReverseIterator& operator++() { mIter--; return *this; } 28 | constexpr auto& operator*() const { return *(mIter - 1); } 29 | constexpr bool operator==(const ReverseIterator&) const = default; 30 | 31 | taIter mIter = {}; 32 | }; 33 | 34 | 35 | // Helper type to iterate backwards on a container. 36 | template 37 | struct ReverseAdapter 38 | { 39 | constexpr auto begin() { return ReverseIterator{ mContainer.end() }; } 40 | constexpr auto end() { return ReverseIterator{ mContainer.begin() }; } 41 | 42 | taContainer& mContainer; 43 | }; 44 | 45 | 46 | // Helper function to iterate backwards on a container. 47 | template 48 | constexpr ReverseAdapter gBackwards(taContainer& ioContainer) { return { ioContainer }; } 49 | 50 | 51 | // Check if a value is present in a vector-like container. 52 | constexpr bool gContains(const auto& ioContainer, const auto& inElem) 53 | { 54 | for (auto& elem : ioContainer) 55 | if (elem == inElem) 56 | return true; 57 | 58 | return false; 59 | } 60 | 61 | 62 | // Check if any element in the container matches the predicate. 63 | bool gAnyOf(const auto& inContainer, const auto& inPredicate) 64 | { 65 | for (auto& element : inContainer) 66 | if (inPredicate(element)) 67 | return true; 68 | return false; 69 | } 70 | 71 | 72 | // Check if none of the elements in the container matches the predicate. 73 | bool gNoneOf(const auto& inContainer, const auto& inPredicate) 74 | { 75 | for (auto& element : inContainer) 76 | if (inPredicate(element)) 77 | return false; 78 | return true; 79 | } 80 | 81 | 82 | // Check if all the elements in the container matches the predicate. 83 | bool gAllOf(const auto& inContainer, const auto& inPredicate) 84 | { 85 | for (auto& element : inContainer) 86 | if (!inPredicate(element)) 87 | return false; 88 | return true; 89 | } 90 | 91 | 92 | // Check if all the elements inside two containers are equal. 93 | constexpr bool gEquals(const auto& inContainerA, const auto& inContainerB) 94 | { 95 | if (inContainerA.Size() != inContainerB.Size()) 96 | return false; 97 | 98 | auto it_a = inContainerA.Begin(); 99 | auto it_b = inContainerB.Begin(); 100 | auto end_a = inContainerA.End(); 101 | while (it_a != end_a) 102 | { 103 | if (*it_a != *it_b) 104 | return false; 105 | 106 | ++it_a; 107 | ++it_b; 108 | } 109 | 110 | return true; 111 | } 112 | 113 | 114 | // Lower bound implementation to avoid 115 | template 116 | constexpr taIterator gLowerBound(taIterator inFirst, taIterator inLast, const auto& inElem) 117 | { 118 | auto first = inFirst; 119 | auto count = inLast - first; 120 | 121 | while (count > 0) 122 | { 123 | auto count2 = count / 2; 124 | auto mid = first + count2; 125 | 126 | if (*mid < inElem) 127 | { 128 | first = mid + 1; 129 | count -= count2 + 1; 130 | } 131 | else 132 | { 133 | count = count2; 134 | } 135 | } 136 | 137 | return first; 138 | } 139 | 140 | 141 | // Find a value in a sorted [inBegin, inEnd) range. 142 | template 143 | constexpr auto gFindSorted(taIterator inBegin, taIterator inEnd, const auto& inElem) 144 | { 145 | auto it = gLowerBound(inBegin, inEnd, inElem); 146 | 147 | if (it != inEnd && *it == inElem) 148 | return it; 149 | else 150 | return inEnd; 151 | } 152 | 153 | 154 | // Find a value in a sorted vector-like container. 155 | constexpr auto gFindSorted(const auto& inContainer, const auto& inElem) 156 | { 157 | return gFindSorted(inContainer.Begin(), inContainer.End(), inElem); 158 | } 159 | 160 | 161 | // Find a value in a sorted vector-like container. 162 | constexpr auto gFindSorted(auto& inContainer, const auto& inElem) 163 | { 164 | return gFindSorted(inContainer.Begin(), inContainer.End(), inElem); 165 | } 166 | 167 | 168 | // Insert a value in a sorted vector-like container. 169 | constexpr auto gEmplaceSorted(auto& ioContainer, const auto& inElem) 170 | { 171 | auto end = ioContainer.End(); 172 | auto it = gLowerBound(ioContainer.Begin(), end, inElem); 173 | 174 | if (it != end && *it == inElem) 175 | return it; 176 | else 177 | { 178 | int index = (int)(it - ioContainer.Begin()); 179 | ioContainer.Emplace(index, inElem); 180 | return ioContainer.Begin() + index; 181 | } 182 | } 183 | 184 | 185 | // Find a value in the [inBegin, inEnd) range. 186 | template 187 | constexpr auto gFind(taIterator inBegin, taIterator inEnd, const auto& inElem) 188 | { 189 | for (auto it = inBegin; it != inEnd; ++it) 190 | { 191 | if (*it == inElem) 192 | return it; 193 | } 194 | 195 | return inEnd; 196 | } 197 | 198 | 199 | // Find a value in a vector-like container. 200 | constexpr auto gFind(auto& inContainer, const auto& inElem) 201 | { 202 | return gFind(inContainer.Begin(), inContainer.End(), inElem); 203 | } 204 | 205 | 206 | // Find a value in a vector-like container. 207 | constexpr auto gFind(const auto& inContainer, const auto& inElem) 208 | { 209 | return gFind(inContainer.Begin(), inContainer.End(), inElem); 210 | } 211 | 212 | 213 | // Erase an element from a vector-like container by swapping it with the last one and reducing the size by 1. 214 | constexpr void gSwapErase(auto& inContainer, const auto& inIterator) 215 | { 216 | if (inIterator != (inContainer.End() - 1)) 217 | gSwap(inContainer.Back(), *inIterator); 218 | inContainer.PopBack(); 219 | } 220 | 221 | 222 | // Remove the first value that matches predicate from a vector-like container. 223 | constexpr bool gSwapEraseFirstIf(auto& inContainer, const auto& inPredicate) 224 | { 225 | auto end = inContainer.End(); 226 | auto begin = inContainer.Begin(); 227 | 228 | for (auto it = begin; it != end; ++it) 229 | { 230 | if (inPredicate(*it)) 231 | { 232 | gSwapErase(inContainer, it); 233 | return true; 234 | } 235 | } 236 | 237 | return false; 238 | } 239 | -------------------------------------------------------------------------------- /Bedrock/Compare.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | 6 | 7 | struct ZeroLiteral 8 | { 9 | // Note: The assert is just there to break compilation if inOrder isn't 0 (because the failing case isn't constexpr code). 10 | consteval ZeroLiteral(int inOrder) { gAssert(inOrder == 0); } 11 | }; 12 | 13 | 14 | #ifdef BEDROCK_ENABLE_STD 15 | 16 | #include 17 | 18 | #else 19 | 20 | namespace std 21 | { 22 | 23 | struct partial_ordering 24 | { 25 | static const partial_ordering less; 26 | static const partial_ordering equivalent; 27 | static const partial_ordering greater; 28 | static const partial_ordering unordered; 29 | 30 | friend constexpr bool operator==(partial_ordering, partial_ordering) = default; 31 | friend constexpr bool operator==(partial_ordering inOrder, ZeroLiteral) { return inOrder.mValue == 0; } 32 | 33 | friend constexpr bool operator<(partial_ordering inOrder, ZeroLiteral) { return inOrder == less; } 34 | friend constexpr bool operator>(partial_ordering inOrder, ZeroLiteral) { return inOrder.mValue > 0; } 35 | 36 | friend constexpr bool operator<=(partial_ordering inOrder, ZeroLiteral) 37 | { 38 | return inOrder == equivalent || inOrder == less; 39 | } 40 | friend constexpr bool operator>=(partial_ordering inOrder, ZeroLiteral) { return inOrder.mValue >= 0; } 41 | 42 | friend constexpr bool operator<(ZeroLiteral, partial_ordering inOrder) { return inOrder.mValue > 0; } 43 | friend constexpr bool operator>(ZeroLiteral, partial_ordering inOrder) { return inOrder == less; } 44 | 45 | friend constexpr bool operator<=(ZeroLiteral, partial_ordering inOrder) { return inOrder.mValue >= 0; } 46 | friend constexpr bool operator>=(ZeroLiteral, partial_ordering inOrder) 47 | { 48 | return inOrder == equivalent || inOrder == less; 49 | } 50 | 51 | friend constexpr partial_ordering operator<=>(partial_ordering inOrder, ZeroLiteral) { return inOrder; } 52 | friend constexpr partial_ordering operator<=>(ZeroLiteral, partial_ordering inOrder) 53 | { 54 | if (inOrder == less) 55 | return greater; 56 | 57 | if (inOrder == greater) 58 | return less; 59 | 60 | return inOrder; 61 | } 62 | 63 | signed char mValue; 64 | }; 65 | 66 | inline constexpr partial_ordering partial_ordering::less = { -1 }; 67 | inline constexpr partial_ordering partial_ordering::equivalent = { 0 }; 68 | inline constexpr partial_ordering partial_ordering::greater = { 1 }; 69 | inline constexpr partial_ordering partial_ordering::unordered = { -128 }; 70 | 71 | 72 | struct weak_ordering 73 | { 74 | static const weak_ordering less; 75 | static const weak_ordering equivalent; 76 | static const weak_ordering greater; 77 | 78 | constexpr operator partial_ordering() const { return { mValue }; } 79 | 80 | friend constexpr bool operator==(weak_ordering, weak_ordering) = default; 81 | friend constexpr bool operator==(weak_ordering inOrder, ZeroLiteral) { return inOrder.mValue == 0; } 82 | 83 | friend constexpr bool operator<(weak_ordering inOrder, ZeroLiteral) { return inOrder.mValue < 0; } 84 | friend constexpr bool operator>(weak_ordering inOrder, ZeroLiteral) { return inOrder.mValue > 0; } 85 | 86 | friend constexpr bool operator<=(weak_ordering inOrder, ZeroLiteral) { return inOrder.mValue <= 0; } 87 | friend constexpr bool operator>=(weak_ordering inOrder, ZeroLiteral) { return inOrder.mValue >= 0; } 88 | 89 | friend constexpr bool operator<(ZeroLiteral, weak_ordering inOrder) { return inOrder.mValue > 0; } 90 | friend constexpr bool operator>(ZeroLiteral, weak_ordering inOrder) { return inOrder.mValue < 0; } 91 | 92 | friend constexpr bool operator<=(ZeroLiteral, weak_ordering inOrder) { return inOrder.mValue >= 0; } 93 | friend constexpr bool operator>=(ZeroLiteral, weak_ordering inOrder) { return inOrder.mValue <= 0; } 94 | 95 | friend constexpr weak_ordering operator<=>(weak_ordering inOrder, ZeroLiteral) { return inOrder; } 96 | friend constexpr weak_ordering operator<=>(ZeroLiteral, weak_ordering inOrder) 97 | { 98 | return { (signed char)(-inOrder.mValue) }; 99 | } 100 | 101 | signed char mValue; 102 | }; 103 | 104 | inline constexpr weak_ordering weak_ordering::less = { -1 }; 105 | inline constexpr weak_ordering weak_ordering::equivalent = { 0 }; 106 | inline constexpr weak_ordering weak_ordering::greater = { 1 }; 107 | 108 | 109 | struct strong_ordering 110 | { 111 | static const strong_ordering less; 112 | static const strong_ordering equal; 113 | static const strong_ordering equivalent; 114 | static const strong_ordering greater; 115 | 116 | constexpr operator partial_ordering() const { return { mValue }; } 117 | constexpr operator weak_ordering() const { return { mValue }; } 118 | 119 | friend constexpr bool operator==(strong_ordering, strong_ordering) = default; 120 | friend constexpr bool operator==(strong_ordering inOrder, ZeroLiteral) { return inOrder.mValue == 0; } 121 | 122 | friend constexpr bool operator<(strong_ordering inOrder, ZeroLiteral) { return inOrder.mValue < 0; } 123 | friend constexpr bool operator>(strong_ordering inOrder, ZeroLiteral) { return inOrder.mValue > 0; } 124 | 125 | friend constexpr bool operator<=(strong_ordering inOrder, ZeroLiteral) { return inOrder.mValue <= 0; } 126 | friend constexpr bool operator>=(strong_ordering inOrder, ZeroLiteral) { return inOrder.mValue >= 0; } 127 | 128 | friend constexpr bool operator<(ZeroLiteral, strong_ordering inOrder) { return inOrder.mValue > 0; } 129 | friend constexpr bool operator>(ZeroLiteral, strong_ordering inOrder) { return inOrder.mValue < 0; } 130 | 131 | friend constexpr bool operator<=(ZeroLiteral, strong_ordering inOrder) { return inOrder.mValue >= 0; } 132 | friend constexpr bool operator>=(ZeroLiteral, strong_ordering inOrder) { return inOrder.mValue <= 0; } 133 | 134 | friend constexpr strong_ordering operator<=>(strong_ordering inOrder, ZeroLiteral) { return inOrder; } 135 | friend constexpr strong_ordering operator<=>(ZeroLiteral, strong_ordering inOrder) 136 | { 137 | return { (signed char)-inOrder.mValue }; 138 | } 139 | 140 | signed char mValue; 141 | }; 142 | 143 | inline constexpr strong_ordering strong_ordering::less = { -1 }; 144 | inline constexpr strong_ordering strong_ordering::equal = { 0 }; 145 | inline constexpr strong_ordering strong_ordering::equivalent = { 0 }; 146 | inline constexpr strong_ordering strong_ordering::greater = { 1 }; 147 | 148 | } 149 | 150 | #endif 151 | 152 | using PartialOrdering = std::partial_ordering; 153 | using WeakOrdering = std::weak_ordering; 154 | using StrongOrdering = std::strong_ordering; 155 | -------------------------------------------------------------------------------- /Bedrock/Allocator.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | 8 | // Default allocator. Allocates from the heap. 9 | template 10 | struct DefaultAllocator 11 | { 12 | // Allocate memory. 13 | static taType* Allocate(int inSize) { return (taType*)gMemAlloc(inSize * sizeof(taType)).mPtr; } 14 | static void Free(taType* inPtr, int inSize) { gMemFree({ (uint8*)inPtr, inSize * (int64)sizeof(taType) }); } 15 | 16 | // Try changing the size of an existing allocation, return false if unsuccessful. 17 | static bool TryRealloc(taType* inPtr, int inCurrentSize, int inNewSize) { gAssert(inPtr != nullptr); return false; } 18 | }; 19 | 20 | 21 | 22 | // Temp memory allocator. Allocates from a thread-local global MemArena. 23 | // Falls back to the default allocator if temp memory runs out. 24 | template 25 | struct TempAllocator 26 | { 27 | // Allocate memory. 28 | static taType* Allocate(int inSize); 29 | static void Free(taType* inPtr, int inSize); 30 | 31 | // Try changing the size of an existing allocation, return false if unsuccessful. 32 | static bool TryRealloc(taType* inPtr, int inCurrentSize, int inNewSize); 33 | }; 34 | 35 | 36 | // Allocates from an externally provided MemArena. 37 | template > 38 | struct ArenaAllocator 39 | { 40 | using MemArenaType = taMemArenaType; 41 | 42 | ArenaAllocator() = default; 43 | ArenaAllocator(MemArenaType& inArena) : mArena(&inArena) {} 44 | 45 | // Allocate memory. 46 | taType* Allocate(int inSize) { return (taType*)mArena->Alloc(inSize * sizeof(taType)).mPtr; } 47 | void Free(taType* inPtr, int inSize) { mArena->Free({ (uint8*)inPtr, inSize * (int64)sizeof(taType) }); } 48 | 49 | // Try changing the size of an existing allocation, return false if unsuccessful. 50 | bool TryRealloc(taType* inPtr, int inCurrentSize, int inNewSize); 51 | 52 | MemArenaType* GetArena() { return mArena; } 53 | const MemArenaType* GetArena() const { return mArena; } 54 | 55 | private: 56 | MemArenaType* mArena = nullptr; 57 | }; 58 | 59 | 60 | // Allocates from an internal VMemArena which uses virtual memory. 61 | // The VMemArena can grow as necessary by committing more virtual memory. 62 | template 63 | struct VMemAllocator 64 | { 65 | using MemArenaType = VMemArena<0>; // Don't need to support any out of order free since the arena isn't shared. 66 | 67 | static constexpr int64 cDefaultReservedSize = MemArenaType::cDefaultReservedSize; // By default the arena will reserve that much virtual memory. 68 | static constexpr int64 cDefaultCommitSize = MemArenaType::cDefaultCommitSize; // By default the arena will commit that much virtual memory every time it grows. 69 | 70 | VMemAllocator() = default; 71 | VMemAllocator(int inReservedSizeInBytes, int inCommitIncreaseSizeInBytes = cDefaultCommitSize) 72 | : mArena(inReservedSizeInBytes, inCommitIncreaseSizeInBytes) {} 73 | 74 | // Allocate memory. 75 | taType* Allocate(int inSize) { return (taType*)mArena.Alloc(inSize * sizeof(taType)).mPtr; } 76 | void Free(taType* inPtr, int inSize) { mArena.Free({ (uint8*)inPtr, inSize * (int64)sizeof(taType) }); } 77 | 78 | // Try changing the size of an existing allocation, return false if unsuccessful. 79 | bool TryRealloc(taType* inPtr, int inCurrentSize, int inNewSize); 80 | 81 | int MaxSize() const { return mArena.GetReservedSize() / sizeof(taType); } 82 | const MemArenaType* GetArena() const { return &mArena; } 83 | 84 | private: 85 | MemArenaType mArena; 86 | }; 87 | 88 | 89 | // Allocates from an internal FixedMemArena. 90 | template 91 | struct FixedAllocator 92 | { 93 | static constexpr int cMemAreanaSizeInBytes = (int)gAlignUp(taSize * sizeof(taType), MemArena<>::cAlignment); 94 | using MemArenaType = FixedMemArena; // Don't need to support any out of order free since the arena isn't shared. 95 | 96 | // Allocate memory. 97 | taType* Allocate(int inSize) { return (taType*)mArena.Alloc(inSize * sizeof(taType)).mPtr; } 98 | void Free(taType* inPtr, int inSize) { mArena.Free({ (uint8*)inPtr, inSize * (int64)sizeof(taType) }); } 99 | 100 | // Try changing the size of an existing allocation, return false if unsuccessful. 101 | bool TryRealloc(taType* inPtr, int inCurrentSize, int inNewSize); 102 | 103 | static int MaxSize() { return taSize; } 104 | const MemArenaType* GetArena() const { return &mArena; } 105 | 106 | private: 107 | MemArenaType mArena; 108 | }; 109 | 110 | 111 | 112 | template 113 | taType* TempAllocator::Allocate(int inSize) 114 | { 115 | MemBlock mem = gTempMemArena.Alloc(inSize * sizeof(taType)); 116 | 117 | if (mem != nullptr) [[likely]] 118 | return (taType*)mem.mPtr; 119 | 120 | return DefaultAllocator::Allocate(inSize); 121 | } 122 | 123 | 124 | template 125 | void TempAllocator::Free(taType* inPtr, int inSize) 126 | { 127 | if (gTempMemArena.Owns(inPtr)) [[likely]] 128 | gTempMemArena.Free({ (uint8*)inPtr, inSize * (int64)sizeof(taType) }); 129 | else 130 | DefaultAllocator::Free(inPtr, inSize); 131 | } 132 | 133 | 134 | template 135 | bool TempAllocator::TryRealloc(taType* inPtr, int inCurrentSize, int inNewSize) 136 | { 137 | gAssert(inPtr != nullptr); // Call Allocate instead. 138 | 139 | if (gTempMemArena.Owns(inPtr)) [[likely]] 140 | { 141 | MemBlock mem = { (uint8*)inPtr, inCurrentSize * (int64)sizeof(taType) }; 142 | 143 | // With the TempAllocator, only try to resize the last alloc! 144 | // Otherwise doing a new alloc is going to waste a lot of memory very quickly. 145 | gAssert(gTempMemArena.IsLastAlloc(mem)); 146 | return gTempMemArena.TryRealloc(mem, inNewSize * sizeof(taType)); 147 | } 148 | 149 | return DefaultAllocator::TryRealloc(inPtr, inCurrentSize, inNewSize); 150 | } 151 | 152 | 153 | template 154 | bool ArenaAllocator::TryRealloc(taType* inPtr, int inCurrentSize, int inNewSize) 155 | { 156 | gAssert(inPtr != nullptr); // Call Allocate instead. 157 | 158 | MemBlock mem = { (uint8*)inPtr, inCurrentSize * (int64)sizeof(taType) }; 159 | return mArena->TryRealloc(mem, inNewSize * sizeof(taType)); 160 | } 161 | 162 | 163 | template 164 | bool VMemAllocator::TryRealloc(taType* inPtr, int inCurrentSize, int inNewSize) 165 | { 166 | gAssert(inPtr != nullptr); // Call Allocate instead. 167 | 168 | MemBlock mem = { (uint8*)inPtr, inCurrentSize * (int64)sizeof(taType) }; 169 | return mArena.TryRealloc(mem, inNewSize * sizeof(taType)); 170 | } 171 | 172 | 173 | template 174 | bool FixedAllocator::TryRealloc(taType* inPtr, int inCurrentSize, int inNewSize) 175 | { 176 | gAssert(inPtr != nullptr); // Call Allocate instead. 177 | 178 | MemBlock mem = { (uint8*)inPtr, inCurrentSize * (int64)sizeof(taType) }; 179 | return mArena.TryRealloc(mem, inNewSize * sizeof(taType)); 180 | } 181 | -------------------------------------------------------------------------------- /Bedrock/HashMap.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | REGISTER_TEST("HashMap") 9 | { 10 | HashMap map; 11 | auto& const_map = const_cast&>(map); 12 | 13 | TEST_TRUE(map.Insert("bread", "butter").mResult == EInsertResult::Added); 14 | TEST_TRUE(map.Insert("bread", "jam").mResult == EInsertResult::Found); 15 | map["toast"] = "rubbish"; 16 | String cheese("cheese"); 17 | TEST_TRUE(map.Insert(StringView("baguette"), cheese).mResult == EInsertResult::Added); 18 | String bagel("bagel"); 19 | TEST_TRUE(map.Insert(bagel, "not sure").mResult == EInsertResult::Added); 20 | TEST_TRUE(map.Emplace("bun", "no").mResult == EInsertResult::Added); 21 | TEST_TRUE(map.Emplace("pretzel", "fine").mResult == EInsertResult::Added); 22 | String brioche("brioche"); 23 | TEST_TRUE(map.Emplace(brioche, "jam").mResult == EInsertResult::Added); 24 | TEST_TRUE(map.InsertOrAssign(brioche, "peanut butter").mResult == EInsertResult::Replaced); 25 | TEST_TRUE(map.InsertOrAssign("croissant", "chocolate").mResult == EInsertResult::Added); 26 | 27 | TEST_TRUE(map.Find("bread")->mValue == "butter"); 28 | TEST_TRUE(const_map.Find("bread")->mValue == "butter"); 29 | TEST_TRUE(map.At("bread") == "butter"); 30 | TEST_TRUE(const_map.At("bread") == "butter"); 31 | map.At("bread") = "jam"; 32 | TEST_TRUE(const_map.At("bread") == "jam"); 33 | TEST_TRUE(map.Find("toast")->mValue == "rubbish"); 34 | TEST_TRUE(map.Find(StringView("baguette"))->mValue == "cheese"); 35 | TEST_TRUE(map.Find(bagel)->mValue == "not sure"); 36 | TEST_TRUE(map["bun"] == "no"); 37 | map["bun"] = "burger"; 38 | TEST_TRUE(map.Find("bun")->mValue == "burger"); 39 | TEST_TRUE(map.Find("pretzel")->mValue == "fine"); 40 | TEST_TRUE(map.Find("brioche")->mValue == "peanut butter"); 41 | TEST_TRUE(map.Find("croissant")->mValue == "chocolate"); 42 | 43 | TEST_TRUE(map.Insert("ciabatta", "is baguette").mResult == EInsertResult::Added); 44 | TEST_TRUE(map.Insert("pain", "perdu").mResult == EInsertResult::Added); 45 | TEST_TRUE(map.Find("broad") == map.End()); 46 | 47 | TEST_TRUE(map.Erase("ciabatta")); 48 | TEST_TRUE(map.Find("ciabatta") == map.End()); 49 | TEST_TRUE(map.Find("pain")->mValue == "perdu"); 50 | TEST_FALSE(map.Erase("broad")); 51 | 52 | TEST_TRUE(map.Erase("bread")); 53 | TEST_TRUE(map.Erase("toast")); 54 | TEST_TRUE(map.Erase("pretzel")); 55 | TEST_TRUE(map.Erase("brioche")); 56 | TEST_TRUE(map.Erase("croissant")); 57 | }; 58 | 59 | 60 | REGISTER_TEST("HashSet Reserve") 61 | { 62 | HashSet set; 63 | set.Insert(42); 64 | 65 | for (int i = 0; i < 100; i++) 66 | { 67 | set.Reserve(i); 68 | TEST_TRUE(set.Capacity() >= i); 69 | TEST_TRUE(set.Contains(42)); 70 | } 71 | }; 72 | 73 | 74 | REGISTER_TEST("HashSet") 75 | { 76 | HashSet set; 77 | 78 | TEST_TRUE(set.Insert("bread").mResult == EInsertResult::Added); 79 | TEST_TRUE(set.Insert("bread").mResult == EInsertResult::Found); 80 | TEST_TRUE(set.Insert(StringView("baguette")).mResult == EInsertResult::Added); 81 | String bagel("bagel"); 82 | TEST_TRUE(set.Insert(bagel).mResult == EInsertResult::Added); 83 | TEST_TRUE(set.Emplace("bun").mResult == EInsertResult::Added); 84 | TEST_TRUE(set.Emplace("pretzel").mResult == EInsertResult::Added); 85 | String brioche("brioche"); 86 | TEST_TRUE(set.Emplace(brioche).mResult == EInsertResult::Added); 87 | 88 | TEST_TRUE(set.Contains("bread")); 89 | TEST_TRUE(set.Contains(StringView("baguette"))); 90 | TEST_TRUE(set.Contains(bagel)); 91 | TEST_TRUE(set.Contains("bun")); 92 | TEST_TRUE(set.Contains("pretzel")); 93 | TEST_TRUE(set.Contains("brioche")); 94 | 95 | TEST_TRUE(set.Insert("ciabatta").mResult == EInsertResult::Added); 96 | TEST_TRUE(set.Insert("pain").mResult == EInsertResult::Added); 97 | TEST_TRUE(set.Find("broad") == set.End()); 98 | 99 | TEST_TRUE(set.Erase("ciabatta")); 100 | TEST_TRUE(set.Find("ciabatta") == set.End()); 101 | TEST_TRUE(set.Contains("pain")); 102 | TEST_FALSE(set.Erase("broad")); 103 | 104 | TEST_TRUE(set.Erase("pretzel")); 105 | TEST_TRUE(set.Erase("bun")); 106 | TEST_TRUE(set.Erase("brioche")); 107 | TEST_TRUE(set.Erase("baguette")); 108 | }; 109 | 110 | 111 | template 112 | static void sLargeHashMapTest(taHashMap& map) 113 | { 114 | constexpr int cSize = 100000; 115 | constexpr int cInitialRandSeed = 42; 116 | 117 | // Fill a map with lots of random values. 118 | int rand_seed = cInitialRandSeed; 119 | for (int i = 0; i < cSize; i++) 120 | { 121 | rand_seed = gRand32(rand_seed); 122 | map.Insert(i, rand_seed); 123 | } 124 | 125 | // Check that all the values are found. 126 | rand_seed = cInitialRandSeed; 127 | for (int i = 0; i < cSize; i++) 128 | { 129 | rand_seed = gRand32(rand_seed); 130 | auto iter = map.Find(i); 131 | TEST_TRUE(iter != map.End()); 132 | TEST_TRUE(iter->mKey == i); 133 | TEST_TRUE(iter->mValue == rand_seed); 134 | } 135 | 136 | // Make a copy 137 | decltype(map) map2 = map; 138 | 139 | // Check that all the values are found in copy. 140 | rand_seed = cInitialRandSeed; 141 | for (int i = 0; i < cSize; i++) 142 | { 143 | rand_seed = gRand32(rand_seed); 144 | auto iter = map2.Find(i); 145 | TEST_TRUE(iter != map2.End()); 146 | TEST_TRUE(iter->mKey == i); 147 | TEST_TRUE(iter->mValue == rand_seed); 148 | } 149 | 150 | // Remove all the values. 151 | for (int i = 0; i < cSize; i++) 152 | { 153 | TEST_TRUE(map.Erase(i)); 154 | } 155 | } 156 | 157 | 158 | REGISTER_TEST("Large HashMap") 159 | { 160 | HashMap map; 161 | sLargeHashMapTest(map); 162 | }; 163 | 164 | 165 | REGISTER_TEST("Large Temp HashMap") 166 | { 167 | TEST_INIT_TEMP_MEMORY(100_KiB); 168 | 169 | TempHashMap map; 170 | sLargeHashMapTest(map); 171 | }; 172 | 173 | 174 | REGISTER_TEST("Large VMem HashMap") 175 | { 176 | TEST_INIT_TEMP_MEMORY(100_KiB); 177 | 178 | VMemHashMap map; 179 | sLargeHashMapTest(map); 180 | }; 181 | 182 | 183 | 184 | static void sLargeHashSetTest(auto& set) 185 | { 186 | constexpr int cSize = 100000; 187 | constexpr int cInitialRandSeed = 42; 188 | 189 | // Fill a map with lots of random values. 190 | int rand_value = cInitialRandSeed; 191 | for (int i = 0; i < cSize; i++) 192 | { 193 | rand_value = gRand32(rand_value); 194 | set.Insert(rand_value); 195 | } 196 | 197 | // Check that all the values are found. 198 | rand_value = cInitialRandSeed; 199 | for (int i = 0; i < cSize; i++) 200 | { 201 | rand_value = gRand32(rand_value); 202 | auto iter = set.Find(rand_value); 203 | TEST_TRUE(iter != set.End()); 204 | TEST_TRUE(*iter == rand_value); 205 | } 206 | 207 | // Remove all the values. 208 | rand_value = cInitialRandSeed; 209 | for (int i = 0; i < cSize; i++) 210 | { 211 | rand_value = gRand32(rand_value); 212 | TEST_TRUE(set.Erase(rand_value)); 213 | } 214 | } 215 | 216 | 217 | REGISTER_TEST("Large HashSet") 218 | { 219 | HashSet set; 220 | sLargeHashSetTest(set); 221 | }; 222 | 223 | 224 | REGISTER_TEST("Large Temp HashSet") 225 | { 226 | TEST_INIT_TEMP_MEMORY(100_KiB); 227 | 228 | TempHashSet set; 229 | sLargeHashSetTest(set); 230 | }; 231 | 232 | 233 | REGISTER_TEST("Large VMem HashSet") 234 | { 235 | TEST_INIT_TEMP_MEMORY(100_KiB); 236 | 237 | VMemHashSet set; 238 | sLargeHashSetTest(set); 239 | }; 240 | -------------------------------------------------------------------------------- /Bedrock/StringView.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | struct StringView 8 | { 9 | constexpr StringView() = default; 10 | constexpr StringView(const StringView&) = default; 11 | constexpr StringView(StringView&&) = default; 12 | constexpr ~StringView() = default; 13 | constexpr StringView& operator=(const StringView&) = default; 14 | constexpr StringView& operator=(StringView&&) = default; 15 | constexpr StringView(const char* inString); 16 | constexpr StringView(const char* inString, int inSize); 17 | constexpr StringView(const char* inBegin, const char* inEnd); 18 | 19 | constexpr int Size() const { return mSize; } 20 | constexpr bool Empty() const { return mSize == 0; } 21 | constexpr const char* AsCStr() const; 22 | constexpr StringView SubStr(int inPosition, int inCount = cMaxInt) const; // Note: negative inCount behaves like cMaxInt 23 | constexpr void RemoveSuffix(int inCount); 24 | constexpr void RemovePrefix(int inCount); 25 | 26 | constexpr const char* Data() const { return mData; } 27 | 28 | constexpr const char* Begin() const { return mData; } 29 | constexpr const char* End() const { return mData + mSize; } 30 | constexpr const char* begin() const { return mData; } 31 | constexpr const char* end() const { return mData + mSize; } 32 | 33 | constexpr char Front() const { gAssert(mSize > 0); return mData[0]; } 34 | constexpr char Back() const { gAssert(mSize > 0); return mData[mSize - 1]; } 35 | 36 | constexpr bool operator==(StringView inOther) const; 37 | constexpr char operator[](int inPosition) const { gBoundsCheck(inPosition, mSize); return mData[inPosition]; } 38 | 39 | constexpr bool operator<(StringView inOther) const; // TODO: replace with <=> 40 | 41 | constexpr int Find(char inCharacter, int inPosition = 0) const; 42 | constexpr int Find(StringView inString, int inPosition = 0) const; 43 | constexpr int FindFirstOf(StringView inCharacters) const; 44 | constexpr int FindLastOf(StringView inCharacters) const; 45 | constexpr int FindFirstNotOf(StringView inCharacters) const; 46 | constexpr int FindLastNotOf(StringView inCharacters) const; 47 | 48 | constexpr bool Contains(StringView inString) const { return Find(inString) != -1; } 49 | constexpr bool Contains(char inCharacter) const { return Find(inCharacter) != -1; } 50 | 51 | constexpr bool StartsWith(StringView inPrefix) const; 52 | constexpr bool EndsWith(StringView inSuffix) const; 53 | 54 | protected: 55 | static constexpr char cEmpty[1] = ""; 56 | 57 | char* mData = const_cast(cEmpty); // Kept mutable only for sharing code with String. 58 | int mSize = 0; 59 | int mCapacity = 1; // Only used by String. 60 | }; 61 | 62 | 63 | // StringView is a contiguous container. 64 | template<> inline constexpr bool cIsContiguous = true; 65 | 66 | constexpr StringView::StringView(const char* inString) 67 | { 68 | mData = const_cast(inString); 69 | mSize = gStrLen(inString); 70 | } 71 | 72 | 73 | constexpr StringView::StringView(const char* inString, int inSize) 74 | { 75 | gAssert(inString != nullptr); 76 | mData = const_cast(inString); 77 | mSize = inSize; 78 | } 79 | 80 | 81 | constexpr StringView::StringView(const char* inBegin, const char* inEnd) 82 | { 83 | gAssert(inBegin != nullptr && inEnd != nullptr); 84 | gAssert(inEnd >= inBegin); 85 | mData = const_cast(inBegin); 86 | mSize = (int)(inEnd - inBegin); 87 | } 88 | 89 | 90 | constexpr const char* StringView::AsCStr() const 91 | { 92 | // All our strings are null terminated so it's "safe", but assert in case it's a sub-string view. 93 | gAssert(*End() == 0); 94 | return mData; 95 | } 96 | 97 | 98 | constexpr bool StringView::operator<(StringView inOther) const 99 | { 100 | int min_size = gMin(mSize, inOther.mSize); 101 | int cmp = gMemCmp(mData, inOther.mData, min_size); 102 | 103 | if (cmp != 0) 104 | return cmp < 0; 105 | 106 | return mSize < inOther.mSize; 107 | } 108 | 109 | 110 | constexpr int StringView::Find(char inCharacter, int inPosition) const 111 | { 112 | const char* iter = gFind(Begin() + inPosition, End(), inCharacter); 113 | if (iter == End()) 114 | return -1; 115 | else 116 | return (int)(iter - Begin()); 117 | } 118 | 119 | 120 | constexpr int StringView::Find(StringView inString, int inPosition) const 121 | { 122 | if (inString.Empty()) 123 | return -1; 124 | 125 | const char searched_first_char = inString[0]; 126 | const int searched_size = inString.Size(); 127 | 128 | int pos = inPosition; 129 | while (true) 130 | { 131 | // Search for the first char. 132 | pos = Find(searched_first_char, pos); 133 | 134 | // If not found, the entire string will not be found. 135 | if (pos == -1) 136 | return -1; 137 | 138 | // If found, check if the searched string follows. 139 | if (SubStr(pos, searched_size) == inString) 140 | return pos; 141 | 142 | // Loop and start searching at the next char. 143 | pos++; 144 | } 145 | } 146 | 147 | 148 | constexpr int StringView::FindFirstOf(StringView inCharacters) const 149 | { 150 | for (const char& c : *this) 151 | { 152 | if (gContains(inCharacters, c)) 153 | return (int)(&c - mData); 154 | } 155 | return -1; 156 | } 157 | 158 | 159 | constexpr int StringView::FindLastOf(StringView inCharacters) const 160 | { 161 | for (const char& c : gBackwards(*this)) 162 | { 163 | if (gContains(inCharacters, c)) 164 | return (int)(&c - mData); 165 | } 166 | return -1; 167 | } 168 | 169 | 170 | constexpr int StringView::FindFirstNotOf(StringView inCharacters) const 171 | { 172 | for (const char& c : *this) 173 | { 174 | if (!gContains(inCharacters, c)) 175 | return (int)(&c - mData); 176 | } 177 | return -1; 178 | } 179 | 180 | 181 | constexpr int StringView::FindLastNotOf(StringView inCharacters) const 182 | { 183 | for (const char& c : gBackwards(*this)) 184 | { 185 | if (!gContains(inCharacters, c)) 186 | return (int)(&c - mData); 187 | } 188 | return -1; 189 | } 190 | 191 | 192 | 193 | constexpr bool StringView::StartsWith(StringView inPrefix) const 194 | { 195 | return SubStr(0, inPrefix.mSize) == inPrefix; 196 | } 197 | 198 | 199 | constexpr bool StringView::EndsWith(StringView inSuffix) const 200 | { 201 | if (mSize < inSuffix.mSize) 202 | return false; 203 | 204 | return SubStr(mSize - inSuffix.mSize, inSuffix.mSize) == inSuffix; 205 | } 206 | 207 | 208 | constexpr bool StringView::operator==(StringView inOther) const 209 | { 210 | if (mSize != inOther.mSize) 211 | return false; 212 | 213 | return gMemCmp(mData, inOther.mData, mSize) == 0; 214 | } 215 | 216 | 217 | constexpr StringView StringView::SubStr(int inPosition, int inCount) const 218 | { 219 | gBoundsCheck(inPosition, mSize + 1); // Note: inPosition == mSize is allowed. 220 | 221 | if (inCount < 0) 222 | inCount = cMaxInt; 223 | 224 | int size = gMin(inCount, mSize - inPosition); 225 | return { mData + inPosition, size }; 226 | } 227 | 228 | 229 | constexpr void StringView::RemoveSuffix(int inCount) 230 | { 231 | gAssert(mSize >= inCount && inCount >= 0); 232 | mSize -= inCount; 233 | } 234 | 235 | 236 | constexpr void StringView::RemovePrefix(int inCount) 237 | { 238 | gAssert(mSize >= inCount && inCount >= 0); 239 | mData += inCount; 240 | mSize -= inCount; 241 | } 242 | 243 | 244 | uint64 gHash(StringView inValue); 245 | 246 | 247 | template <> 248 | struct Hash 249 | { 250 | using IsTransparent = void; 251 | 252 | uint64 operator()(StringView inStr) const { return gHash(inStr); } 253 | uint64 operator()(const char* inStr) const { return gHash(StringView(inStr)); } 254 | }; -------------------------------------------------------------------------------- /Bedrock/Core.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | // Assert macro. 5 | #include 6 | 7 | 8 | // Basic types. 9 | using int8 = signed char; 10 | using uint8 = unsigned char; 11 | using int16 = signed short; 12 | using uint16 = unsigned short; 13 | using int32 = signed long; 14 | using uint32 = unsigned long; 15 | using int64 = signed long long; 16 | using uint64 = unsigned long long; 17 | 18 | using NullPtrType = decltype(nullptr); 19 | 20 | constexpr int8 cMaxInt8 = 0x7F; 21 | constexpr uint8 cMaxUInt8 = 0xFF; 22 | constexpr int16 cMaxInt16 = 0x7FFF; 23 | constexpr uint16 cMaxUInt16 = 0xFFFF; 24 | constexpr int32 cMaxInt32 = 0x7FFFFFFF; 25 | constexpr uint32 cMaxUInt32 = 0xFFFFFFFF; 26 | constexpr int64 cMaxInt64 = 0x7FFFFFFFFFFFFFFF; 27 | constexpr uint64 cMaxUInt64 = 0xFFFFFFFFFFFFFFFF; 28 | 29 | constexpr int cMaxInt = cMaxInt32; 30 | 31 | 32 | // Force a function to be inlined. 33 | // Note: With MSVC this doesn't work in non-optimized builds unless /d2Obforceinline is used. 34 | #ifdef __clang__ 35 | #define force_inline inline __attribute__((always_inline)) 36 | #elif _MSC_VER 37 | #define force_inline inline __forceinline 38 | #else 39 | #error Unknown compiler 40 | #endif 41 | 42 | 43 | // Force a function to not be inlined. 44 | #ifdef __clang__ 45 | #define no_inline __attribute__ ((noinline)) 46 | #elif _MSC_VER 47 | #define no_inline __declspec(noinline) 48 | #else 49 | #error Unknown compiler 50 | #endif 51 | 52 | 53 | // Preprocessor utilities. 54 | #define TOKEN_PASTE1(x, y) x ## y 55 | #define TOKEN_PASTE(x, y) TOKEN_PASTE1(x, y) 56 | 57 | 58 | namespace Details 59 | { 60 | struct DeferDummy {}; 61 | template struct Deferrer { F f; ~Deferrer() { f(); } }; 62 | template Deferrer operator*(DeferDummy, F f) { return {f}; } 63 | } 64 | // Defer execution of a block of code to the end of the scope. 65 | // eg. defer { delete ptr; }; 66 | #define defer auto TOKEN_PASTE(deferred, __LINE__) = Details::DeferDummy{} *[&]() 67 | 68 | 69 | namespace Details 70 | { 71 | struct OnceDummy {}; 72 | template struct Initializer { Initializer(F f) { f(); } }; 73 | template Initializer operator*(OnceDummy, F f) { return {f}; } 74 | } 75 | // Execute a block of code only once. 76 | // eg. do_once { printf("hello\n"); }; 77 | #define do_once static auto TOKEN_PASTE(initializer, __LINE__) = Details::OnceDummy{} *[&]() 78 | 79 | 80 | // Inherit to disallow copies (and moves, implicitly). 81 | struct NoCopy 82 | { 83 | NoCopy() = default; 84 | ~NoCopy() = default; 85 | NoCopy(const NoCopy&) = delete; 86 | NoCopy& operator=(const NoCopy&) = delete; 87 | }; 88 | 89 | 90 | // Inherit to disallow copies but allow moves. 91 | struct MoveOnly 92 | { 93 | MoveOnly() = default; 94 | ~MoveOnly() = default; 95 | MoveOnly(MoveOnly&&) = default; 96 | MoveOnly& operator=(MoveOnly&&) = default; 97 | MoveOnly(const NoCopy&) = delete; 98 | MoveOnly& operator=(const NoCopy&) = delete; 99 | }; 100 | 101 | 102 | // Literals for memory sizes. 103 | consteval int64 operator ""_B(unsigned long long inValue) { return (int64)inValue; } 104 | consteval int64 operator ""_KiB(unsigned long long inValue) { return (int64)inValue * 1024; } 105 | consteval int64 operator ""_MiB(unsigned long long inValue) { return (int64)inValue * 1024 * 1024; } 106 | consteval int64 operator ""_GiB(unsigned long long inValue) { return (int64)inValue * 1024 * 1024 * 1024; } 107 | 108 | 109 | // Basic functions. 110 | template constexpr T gMin(T inA, T inB) { return inA < inB ? inA : inB; } 111 | template constexpr T gMax(T inA, T inB) { return inB < inA ? inA : inB; } 112 | template constexpr T gClamp(T inV, T inLow, T inHigh) { return (inV < inLow) ? inLow : (inHigh < inV) ? inHigh : inV; } 113 | 114 | 115 | // Helper to get the size of C arrays. 116 | template 117 | consteval int64 gElemCount(const taType (&)[taArraySize]) { return taArraySize; } 118 | 119 | 120 | // Return true if the calling function is evaluated in a constexpr context. 121 | // Equivalent to std::is_constant_evaluated. 122 | constexpr bool gIsContantEvaluated() { return __builtin_is_constant_evaluated(); } 123 | 124 | 125 | // Equivalent to std::type_identity 126 | // TODO: move to TypeTraits.h once the bit twiddling functions are moved to a separate header 127 | namespace Details 128 | { 129 | template struct Identity { using Type = T; }; 130 | } 131 | template using Identity = typename Details::Identity::Type; 132 | 133 | // Bit twiddling. Move elsewhere? 134 | template constexpr bool gIsPow2(T inValue) { return inValue != 0 && (inValue & (inValue - 1)) == 0; } 135 | template constexpr T gAlignUp(T inValue, Identity inPow2Alignment) { return (inValue + (inPow2Alignment - 1)) & ~(inPow2Alignment - 1); } 136 | template constexpr T gAlignDown(T inValue, Identity inPow2Alignment) { return inValue & ~(inPow2Alignment - 1); } 137 | 138 | 139 | constexpr int gCountLeadingZeros64(uint64 inValue) 140 | { 141 | if (gIsContantEvaluated()) 142 | { 143 | int leading_zeroes = 0; 144 | for (; leading_zeroes < 64; leading_zeroes++) 145 | { 146 | if (inValue & ((uint64)1 << (63 - leading_zeroes))) 147 | break; // Found a one. 148 | } 149 | return leading_zeroes; 150 | } 151 | 152 | #ifdef __clang__ 153 | 154 | // Note: __builtin_clz is undefined behavior for 0. 155 | if (inValue == 0) [[unlikely]] 156 | return 0; 157 | 158 | return __builtin_clzll(inValue); 159 | 160 | #elif _MSC_VER 161 | 162 | unsigned char _BitScanReverse64(unsigned long* _Index, unsigned __int64 _Mask); 163 | uint32 index; 164 | _BitScanReverse64(&index, inValue); 165 | return 63 - index; 166 | 167 | #else 168 | #error Unknown compiler 169 | #endif 170 | } 171 | 172 | 173 | constexpr int64 gGetNextPow2(int64 inValue) 174 | { 175 | if (inValue <= 1) [[unlikely]] 176 | return 1; 177 | return (int64)1 << (64 - gCountLeadingZeros64(inValue - 1)); 178 | } 179 | 180 | 181 | // Some useful C std function replacements to avoid an include or because the real ones aren't constexpr. 182 | force_inline constexpr int gStrLen(const char* inString) { return (int)__builtin_strlen(inString); } 183 | force_inline constexpr int gMemCmp(const void* inPtrA, const void* inPtrB, int inSize) { return __builtin_memcmp(inPtrA, inPtrB, inSize); } 184 | extern "C" void* __cdecl memcpy(void* inDest, void const* inSource, size_t inSize); 185 | extern "C" void* __cdecl memmove(void* inDest, void const* inSource, size_t inSize); 186 | force_inline void gMemCopy(void* inDest, const void* inSource, int inSize) { memcpy(inDest, inSource, inSize); } 187 | force_inline void gMemMove(void* inDest, const void* inSource, int inSize) { memmove(inDest, inSource, inSize); } 188 | 189 | 190 | // We want some no-op functions (like gMove or gToUnderlying) to be always inlined, but force_inline doesn't work in debug with MSVC by default. 191 | // [[msvc::intrinsic]] works however (as long as the functions only does a static_cast), so it's a better solution in this case. 192 | #ifdef __clang__ 193 | #define ATTRIBUTE_INTRINSIC force_inline 194 | #elif _MSC_VER 195 | #define ATTRIBUTE_INTRINSIC [[msvc::intrinsic]] 196 | #else 197 | #define ATTRIBUTE_INTRINSIC 198 | #endif 199 | 200 | 201 | // Lifetimebound annotation. 202 | #ifdef __clang__ 203 | #define ATTRIBUTE_LIFETIMEBOUND [[clang::lifetimebound]] 204 | #elif _MSC_VER 205 | #define ATTRIBUTE_LIFETIMEBOUND [[msvc::lifetimebound]] 206 | #else 207 | #define ATTRIBUTE_LIFETIMEBOUND 208 | #endif 209 | 210 | -------------------------------------------------------------------------------- /Bedrock/Atomic.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | #define PUSH_DISABLE_DEPRECATED_WARNING __pragma(warning(push)) __pragma(warning(disable : 4996)) 8 | #define POP_WARNING __pragma(warning(pop)) 9 | 10 | 11 | extern "C" 12 | { 13 | char _InterlockedExchange8(char volatile*, char); 14 | char _InterlockedExchangeAdd8(char volatile*, char); 15 | char _InterlockedCompareExchange8(char volatile*, char, char); 16 | long _InterlockedExchange(long volatile*, long); 17 | long _InterlockedExchangeAdd(long volatile*, long); 18 | long _InterlockedCompareExchange(long volatile*, long, long); 19 | __int64 _InterlockedExchange64(__int64 volatile*, __int64); 20 | __int64 _InterlockedExchangeAdd64(__int64 volatile*, __int64); 21 | __int64 _InterlockedCompareExchange64(__int64 volatile*, __int64, __int64); 22 | __int8 __iso_volatile_load8(const volatile __int8*); 23 | __int32 __iso_volatile_load32(const volatile __int32*); 24 | __int64 __iso_volatile_load64(const volatile __int64*); 25 | void __iso_volatile_store8(volatile __int8*, __int8); 26 | void __iso_volatile_store32(volatile __int32*, __int32); 27 | void __iso_volatile_store64(volatile __int64*, __int64); 28 | void _ReadWriteBarrier(); 29 | } 30 | 31 | 32 | #define COMPILER_BARRIER() PUSH_DISABLE_DEPRECATED_WARNING _ReadWriteBarrier() POP_WARNING 33 | 34 | template 35 | struct Atomic; 36 | 37 | enum class MemoryOrder 38 | { 39 | Relaxed, 40 | //Consume, // TODO? 41 | //Acquire, 42 | //Release, 43 | //AcqRel, 44 | SeqCst, 45 | }; 46 | 47 | 48 | // Atomic template class. 49 | // Supports integral types (including bool), pointers and trivially copyable classes <= 8 bytes. Lock-free only. 50 | template 51 | struct Atomic : NoCopy 52 | { 53 | static_assert(cIsTriviallyCopyable); 54 | static_assert(sizeof(taType) == 8 || sizeof(taType) == 4 || sizeof(taType) == 1); // Note: int16 not supported only because not needed so far. 55 | 56 | using ValueType = taType; 57 | using StorageType = Conditional>; // An integral type that can hold the value. 58 | 59 | static_assert(sizeof(ValueType) == sizeof(StorageType)); 60 | 61 | Atomic() = default; 62 | ~Atomic() = default; 63 | Atomic(ValueType inValue) : mValue(inValue) {} 64 | 65 | ValueType Load(MemoryOrder inOrder = MemoryOrder::SeqCst) const; 66 | void Store(ValueType inValue, MemoryOrder inOrder = MemoryOrder::SeqCst); 67 | 68 | ValueType Exchange(ValueType inValue, MemoryOrder inOrder = MemoryOrder::SeqCst); 69 | bool CompareExchange(ValueType& ioExpected, ValueType inDesired); 70 | 71 | ValueType Add(ValueType inValue, MemoryOrder inOrder = MemoryOrder::SeqCst) requires cIsIntegral && (!cIsSame); 72 | ValueType Sub(ValueType inValue, MemoryOrder inOrder = MemoryOrder::SeqCst) requires cIsIntegral && (!cIsSame); 73 | 74 | ValueType Max(ValueType inValue, MemoryOrder inOrder = MemoryOrder::SeqCst) requires cIsIntegral && (!cIsSame); 75 | ValueType Min(ValueType inValue, MemoryOrder inOrder = MemoryOrder::SeqCst) requires cIsIntegral && (!cIsSame); 76 | 77 | private: 78 | static force_inline ValueType sAsValue(StorageType inStorage); 79 | static force_inline StorageType sAsStorage(ValueType inValue); 80 | 81 | ValueType mValue; // Note: This could be StorageType, but ValueType looks nicer in the debugger. 82 | }; 83 | 84 | 85 | // Typedefs for convenience. 86 | using AtomicInt8 = Atomic; 87 | using AtomicInt32 = Atomic; 88 | using AtomicInt64 = Atomic; 89 | using AtomicBool = Atomic; 90 | 91 | 92 | template 93 | typename Atomic::ValueType Atomic::Load(MemoryOrder inOrder) const 94 | { 95 | StorageType value; 96 | auto storage_ptr = (const StorageType*)&mValue; 97 | 98 | if constexpr(sizeof(taType) == 8) 99 | value = __iso_volatile_load64(storage_ptr); 100 | else if constexpr(sizeof(taType) == 4) 101 | value = __iso_volatile_load32((const int*)storage_ptr); 102 | else 103 | value = __iso_volatile_load8(storage_ptr); 104 | 105 | switch (inOrder) 106 | { 107 | case MemoryOrder::Relaxed: break; 108 | case MemoryOrder::SeqCst: COMPILER_BARRIER(); break; 109 | } 110 | 111 | return sAsValue(value); 112 | } 113 | 114 | 115 | template 116 | void Atomic::Store(ValueType inValue, MemoryOrder inOrder) 117 | { 118 | StorageType new_value = sAsStorage(inValue); 119 | auto storage_ptr = (StorageType*)&mValue; 120 | 121 | switch (inOrder) 122 | { 123 | case MemoryOrder::Relaxed: 124 | { 125 | if constexpr(sizeof(taType) == 8) 126 | __iso_volatile_store64(storage_ptr, new_value); 127 | else if constexpr(sizeof(taType) == 4) 128 | __iso_volatile_store32((int*)storage_ptr, new_value); 129 | else 130 | __iso_volatile_store8(storage_ptr, new_value); 131 | break; 132 | } 133 | 134 | case MemoryOrder::SeqCst: 135 | if constexpr(sizeof(taType) == 8) 136 | _InterlockedExchange64(storage_ptr, new_value); 137 | else if constexpr(sizeof(taType) == 4) 138 | _InterlockedExchange(storage_ptr, new_value); 139 | else 140 | _InterlockedExchange8(storage_ptr, new_value); 141 | break; 142 | } 143 | } 144 | 145 | 146 | template 147 | typename Atomic::ValueType Atomic::Exchange(ValueType inValue, MemoryOrder inOrder) 148 | { 149 | StorageType new_value = sAsStorage(inValue); 150 | auto storage_ptr = (StorageType*)&mValue; 151 | StorageType previous_value; 152 | 153 | if constexpr(sizeof(taType) == 8) 154 | previous_value = _InterlockedExchange64(storage_ptr, new_value); 155 | else if constexpr(sizeof(taType) == 4) 156 | previous_value = _InterlockedExchange(storage_ptr, new_value); 157 | else 158 | previous_value = _InterlockedExchange8(storage_ptr, new_value); 159 | 160 | return sAsValue(previous_value); 161 | } 162 | 163 | 164 | template 165 | bool Atomic::CompareExchange(ValueType& ioExpected, ValueType inDesired) 166 | { 167 | StorageType expected = sAsStorage(ioExpected); 168 | StorageType desired = sAsStorage(inDesired); 169 | auto storage_ptr = (StorageType*)&mValue; 170 | StorageType previous_value; 171 | 172 | if constexpr(sizeof(taType) == 8) 173 | previous_value = _InterlockedCompareExchange64(storage_ptr, desired, expected); 174 | else if constexpr(sizeof(taType) == 4) 175 | previous_value = _InterlockedCompareExchange(storage_ptr, desired, expected); 176 | else 177 | previous_value = _InterlockedCompareExchange8(storage_ptr, desired, expected); 178 | 179 | bool exchanged = (previous_value == expected); 180 | 181 | if (!exchanged) 182 | ioExpected = sAsValue(previous_value); 183 | 184 | return exchanged; 185 | } 186 | 187 | 188 | template 189 | typename Atomic::ValueType Atomic::Add(ValueType inValue, MemoryOrder inOrder) requires cIsIntegral && (!cIsSame) 190 | { 191 | StorageType value_to_add = sAsStorage(inValue); 192 | auto storage_ptr = (StorageType*)&mValue; 193 | StorageType previous_value; 194 | 195 | if constexpr(sizeof(taType) == 8) 196 | previous_value = _InterlockedExchangeAdd64(storage_ptr, value_to_add); 197 | else if constexpr(sizeof(taType) == 4) 198 | previous_value = _InterlockedExchangeAdd(storage_ptr, value_to_add); 199 | else 200 | previous_value = _InterlockedExchangeAdd8(storage_ptr, value_to_add); 201 | 202 | return sAsValue(previous_value); 203 | } 204 | 205 | template 206 | typename Atomic::ValueType Atomic::Sub(ValueType inValue, MemoryOrder inOrder) requires cIsIntegral && (!cIsSame) 207 | { 208 | return Add(-inValue, inOrder); 209 | } 210 | 211 | 212 | template 213 | typename Atomic::ValueType Atomic::Max(ValueType inValue, MemoryOrder inOrder) requires cIsIntegral && (!cIsSame) 214 | { 215 | taType prev_value = Load(MemoryOrder::Relaxed); 216 | 217 | while (prev_value < inValue && !CompareExchange(prev_value, inValue)) {} 218 | 219 | return prev_value; 220 | } 221 | 222 | 223 | template 224 | typename Atomic::ValueType Atomic::Min(ValueType inValue, MemoryOrder inOrder) requires cIsIntegral && (!cIsSame) 225 | { 226 | taType prev_value = Load(MemoryOrder::Relaxed); 227 | 228 | while (prev_value > inValue && !CompareExchange(prev_value, inValue)) {} 229 | 230 | return prev_value; 231 | } 232 | 233 | 234 | 235 | template 236 | typename Atomic::ValueType Atomic::sAsValue(StorageType inStorage) 237 | { 238 | if constexpr (cIsIntegral || cIsEnum) 239 | return static_cast(inStorage); 240 | else if constexpr (cIsPointer) 241 | return reinterpret_cast(inStorage); 242 | else 243 | { 244 | // The only way of converting unrelated types that is non UB is memcpy. 245 | // This will be optimized out in practice. 246 | ValueType value; 247 | memcpy(&value, &inStorage, sizeof(inStorage)); 248 | return value; 249 | } 250 | } 251 | 252 | 253 | template 254 | typename Atomic::StorageType Atomic::sAsStorage(ValueType inValue) 255 | { 256 | if constexpr (cIsIntegral || cIsEnum) 257 | return static_cast(inValue); 258 | else if constexpr (cIsPointer) 259 | return reinterpret_cast(inValue); 260 | else 261 | { 262 | // The only way of converting unrelated types that is non UB is memcpy. 263 | // This will be optimized out in practice. 264 | StorageType storage; 265 | memcpy(&storage, &inValue, sizeof(inValue)); 266 | return storage; 267 | } 268 | } 269 | 270 | -------------------------------------------------------------------------------- /Bedrock/String.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // Base class for String. 9 | template struct StringBase; 10 | 11 | 12 | // String class using the DefaultAllocator (heap). 13 | using String = StringBase>; 14 | 15 | // Alias for a String using the TempAllocator. 16 | // Resizable cheaply as long as it's the last Temp allocation. Allocates from the heap as a fallback. 17 | using TempString = StringBase>; 18 | 19 | // Alias for a String using a FixedAllocator. 20 | // It contains a fixed size buffer that can hold taCapacity, including the null terminator. 21 | template 22 | using FixedString = StringBase>; 23 | 24 | 25 | template 26 | struct StringBase : StringView, private taAllocator 27 | { 28 | using Allocator = taAllocator; 29 | 30 | // Default 31 | constexpr StringBase() = default; 32 | ~StringBase(); 33 | 34 | // Move 35 | StringBase(StringBase&& ioOther); 36 | StringBase& operator=(StringBase&& ioOther); 37 | 38 | // Copy 39 | StringBase(const StringBase& inOther) : StringBase(static_cast(inOther)) {} 40 | StringBase& operator=(const StringBase& inOther) { *this = static_cast(inOther); return *this; } 41 | 42 | // Copy from String with different allocator 43 | template 44 | requires (!cIsSame) 45 | StringBase(const StringBase& inOther) : StringBase(static_cast(inOther)) {} 46 | template 47 | requires (!cIsSame) 48 | StringBase& operator=(const StringBase& inOther) { *this = static_cast(inOther); return *this; } 49 | 50 | // Copy from StringView 51 | StringBase(const StringView& inString); 52 | StringBase& operator=(const StringView& inString); 53 | 54 | // Copy from const char* 55 | StringBase(const char* inString) : StringBase(StringView(inString)) {} 56 | StringBase(const char* inString, int inSize) : StringBase(StringView(inString, inSize)) {} 57 | StringBase& operator=(const char* inString) { *this = StringView(inString); return *this; } 58 | 59 | // Copy from InitializerList (not super useful but resolves ambiguity when using = {}) 60 | StringBase(InitializerList inInitializerList); 61 | StringBase& operator=(InitializerList inInitializerList); 62 | 63 | Allocator& GetAllocator() { return *this; } 64 | const Allocator& GetAllocator() const { return *this; } 65 | 66 | constexpr void RemoveSuffix(int inCount); 67 | 68 | constexpr char* Data() const { return mData; } 69 | constexpr char* Begin() { return mData; } 70 | constexpr char* End() { return mData + mSize; } 71 | constexpr char* begin() { return mData; } 72 | constexpr char* end() { return mData + mSize; } 73 | using StringView::Begin; 74 | using StringView::End; 75 | using StringView::begin; 76 | using StringView::end; 77 | 78 | constexpr char& Front() { gAssert(mSize > 0); return mData[0]; } 79 | constexpr char& Back() { gAssert(mSize > 0); return mData[mSize - 1]; } 80 | using StringView::Front; 81 | using StringView::Back; 82 | 83 | char& operator[](int inPosition) { gBoundsCheck(inPosition, mSize); return mData[inPosition]; } 84 | using StringView::operator[]; 85 | 86 | void Reserve(int inCapacity); // Note: inCapacity includes the null terminator. 87 | void Resize(int inSize); // Note: inSize does not include the null terminator (it is stored at [Size()]). 88 | void Clear() { Resize(0); } 89 | void ShrinkToFit(); // Note: Only does somethig if the allocator supports TryRealloc (eg. TempAllocator). 90 | 91 | void Insert(int inPosition, StringView inString); 92 | 93 | int Capacity() const { return mCapacity; } 94 | 95 | static constexpr bool cHasMaxSize = requires { taAllocator().MaxSize(); }; 96 | 97 | // Return the max size that this string can have. 98 | // Note: This method only exists for allocators that have an actual max size. 99 | int MaxSize() const requires cHasMaxSize 100 | { 101 | // Note: -1 because null terminator isn't counted in the size. 102 | return GetAllocator().MaxSize() - 1; 103 | } 104 | 105 | void Append(StringView inString); 106 | void Append(const char* inString, int inSize) { Append(StringView(inString, inSize)); } 107 | void operator+=(StringView inString) { Append(inString); } 108 | 109 | private: 110 | void MoveFrom(StringBase&& ioOther); 111 | void CopyFrom(StringView inOther); 112 | 113 | using StringView::RemovePrefix; // Doesn't work on String as it just increments mData. 114 | }; 115 | 116 | 117 | static_assert(sizeof(StringBase>) == 16); 118 | 119 | // String is a contiguous container. 120 | template inline constexpr bool cIsContiguous> = true; 121 | 122 | 123 | template 124 | struct Hash> : Hash {}; 125 | 126 | 127 | template 128 | StringBase::~StringBase() 129 | { 130 | if (mData != cEmpty) 131 | Allocator::Free(mData, mCapacity); 132 | } 133 | 134 | 135 | template 136 | StringBase::StringBase(StringBase&& ioOther) 137 | { 138 | MoveFrom(gMove(ioOther)); 139 | } 140 | 141 | 142 | template 143 | StringBase& StringBase::operator=(StringBase&& ioOther) 144 | { 145 | MoveFrom(gMove(ioOther)); 146 | return *this; 147 | } 148 | 149 | 150 | template 151 | StringBase::StringBase(const StringView& inString) 152 | { 153 | CopyFrom(inString); 154 | } 155 | 156 | 157 | template 158 | StringBase& StringBase::operator=(const StringView& inString) 159 | { 160 | CopyFrom(inString); 161 | return *this; 162 | } 163 | 164 | 165 | template 166 | StringBase::StringBase(InitializerList inInitializerList) 167 | { 168 | // Make sure we don't build a StringView from a nullptr. 169 | if (inInitializerList.size() != 0) 170 | CopyFrom(StringView(inInitializerList.begin(), (int)inInitializerList.size())); 171 | } 172 | 173 | 174 | template 175 | StringBase& StringBase::operator=(InitializerList inInitializerList) 176 | { 177 | // Make sure we don't build a StringView from a nullptr. 178 | if (inInitializerList.size() != 0) 179 | CopyFrom(StringView(inInitializerList.begin(), (int)inInitializerList.size())); 180 | else 181 | Resize(0); 182 | 183 | return *this; 184 | } 185 | 186 | 187 | // Note: inCapacity includes the null terminator. 188 | template 189 | void StringBase::Reserve(int inCapacity) 190 | { 191 | if (mCapacity >= inCapacity) 192 | return; // Capacity is already enough, early out. 193 | 194 | int old_capacity = mCapacity; 195 | mCapacity = inCapacity; 196 | 197 | gAssert(inCapacity > 1); // Reserving for storing an empty string? That should not happen. 198 | 199 | // Try to grow the allocation. 200 | if (mData != cEmpty && Allocator::TryRealloc(mData, old_capacity, mCapacity)) 201 | return; // Success, nothing else to do. 202 | 203 | // Allocate new data. 204 | char* old_data = mData; 205 | mData = Allocator::Allocate(mCapacity); 206 | 207 | // Copy old data to new. 208 | gMemCopy(mData, old_data, mSize); 209 | mData[mSize] = 0; 210 | 211 | // Free old data. 212 | if (old_data != cEmpty) 213 | Allocator::Free(old_data, old_capacity); 214 | } 215 | 216 | 217 | template 218 | void StringBase::Resize(int inSize) 219 | { 220 | if (inSize == 0 && mData == cEmpty) 221 | return; 222 | 223 | Reserve(inSize + 1); 224 | 225 | mData[inSize] = 0; 226 | mSize = inSize; 227 | } 228 | 229 | 230 | template void StringBase::ShrinkToFit() 231 | { 232 | if (mData == cEmpty || mCapacity == (mSize + 1)) 233 | return; 234 | 235 | if (Allocator::TryRealloc(mData, mCapacity, mSize + 1)) 236 | mCapacity = mSize + 1; 237 | } 238 | 239 | 240 | template 241 | void StringBase::Insert(int inPosition, StringView inString) 242 | { 243 | if (inString.Empty()) 244 | return; 245 | 246 | gBoundsCheck(inPosition, mSize + 1); 247 | // Copying from self is not allowed. 248 | gAssert(mData > inString.End() || (mData + mCapacity) < inString.Begin() || inString.Empty()); 249 | 250 | Reserve(mSize + inString.Size() + 1); 251 | 252 | // Move existing elements to free inPosition. 253 | // Note: if we're inserting at the end, this is just moving the null terminator. 254 | gMemMove(mData + inPosition + inString.Size(), mData + inPosition, mSize - inPosition + 1); 255 | 256 | // Copy the string at inPosition. 257 | gMemCopy(mData + inPosition, inString.Data(), inString.Size()); 258 | 259 | mSize += inString.Size(); 260 | } 261 | 262 | 263 | template 264 | void StringBase::Append(StringView inString) 265 | { 266 | if (inString.Empty()) 267 | return; 268 | 269 | Reserve(mSize + inString.Size() + 1); 270 | 271 | gMemCopy(mData + mSize, inString.Data(), inString.Size()); 272 | mSize += inString.Size(); 273 | mData[mSize] = 0; 274 | } 275 | 276 | 277 | template 278 | void StringBase::MoveFrom(StringBase&& ioOther) 279 | { 280 | if (ioOther.mData == cEmpty) 281 | { 282 | if (mData == cEmpty) 283 | return; // Nothing to do, moving an empty string to an empty string. 284 | 285 | // Moving an empty string to a non-empty string, don't actually move, keep the memory. 286 | mSize = 0; 287 | mData[mSize] = 0; 288 | } 289 | else 290 | { 291 | // Moving from self is not allowed. 292 | gAssert(mData != ioOther.mData); 293 | 294 | // Free the existing data. 295 | if (mData != cEmpty) 296 | Allocator::Free(mData, mCapacity); 297 | 298 | // Move the allocator. 299 | GetAllocator() = gMove(ioOther.GetAllocator()); 300 | ioOther.GetAllocator() = Allocator(); 301 | 302 | mData = ioOther.mData; 303 | mSize = ioOther.mSize; 304 | mCapacity = ioOther.mCapacity; 305 | 306 | ioOther.mData = const_cast(cEmpty); 307 | ioOther.mSize = 0; 308 | ioOther.mCapacity = 1; 309 | } 310 | } 311 | 312 | 313 | template 314 | void StringBase::CopyFrom(StringView inOther) 315 | { 316 | // Copying from self is not allowed (unless it's cEmpty). 317 | gAssert(Begin() > inOther.End() || End() < inOther.Begin() || mData == cEmpty); 318 | 319 | Resize(0); // Makes sure Reserve doesn't have to copy data over to a new allocation. 320 | 321 | // If copying from an empty string, nothing to do. 322 | if (inOther.Empty()) 323 | return; 324 | 325 | Reserve(inOther.Size() + 1); 326 | 327 | mSize = inOther.Size(); 328 | 329 | gMemCopy(mData, inOther.Begin(), mSize); 330 | mData[mSize] = 0; 331 | } 332 | 333 | 334 | template 335 | constexpr void StringBase::RemoveSuffix(int inCount) 336 | { 337 | gAssert(mSize >= inCount); 338 | mSize -= inCount; 339 | mData[mSize] = 0; // Re-null terminate the string. 340 | } 341 | -------------------------------------------------------------------------------- /Bedrock/Vector.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | REGISTER_TEST("Vector") 8 | { 9 | // YOLO compare function. 10 | auto equal = [](const auto& a, const auto& b) -> bool 11 | { 12 | if (a.Size() != b.Size()) 13 | return false; 14 | 15 | for (int i = 0; i < a.Size(); i++) 16 | if (*(a.Begin() + i) != *(b.Begin() + i)) 17 | return false; 18 | 19 | return true; 20 | }; 21 | 22 | { 23 | Vector test = { 1, 2, 3, 4, 5 }; 24 | TEST_TRUE(test.Size() == 5); 25 | TEST_TRUE(test.Capacity() >= 5); 26 | 27 | Vector test_copy = test; 28 | TEST_TRUE(test_copy.Size() == 5); 29 | TEST_TRUE(test_copy.Capacity() >= 5); 30 | TEST_TRUE(Span(test_copy) == Span(test)); 31 | 32 | test_copy = gMove(test); // test_copy should free its previous alloc. Leak detection will tell. 33 | TEST_TRUE(test.Empty()); 34 | } 35 | 36 | { 37 | struct TestStruct 38 | { 39 | explicit TestStruct(int inValue) : mValue(inValue) {} 40 | bool operator==(int inValue) const { return inValue == mValue; } 41 | int mValue; 42 | }; 43 | 44 | Vector test_structs; 45 | test_structs.PushBack(TestStruct{ 0 }); 46 | test_structs.EmplaceBack(1); 47 | test_structs.PushBack(TestStruct{ 2 }); 48 | TEST_TRUE(test_structs.Size() == 3); 49 | TEST_TRUE(test_structs.Capacity() >= 3); 50 | Vector expected = { 0, 1, 2 }; 51 | TEST_TRUE(equal(test_structs, expected)); 52 | 53 | struct MovableOnly : TestStruct 54 | { 55 | MovableOnly(MovableOnly&&) = default; 56 | MovableOnly& operator=(MovableOnly&&) = default; 57 | MovableOnly(const MovableOnly&) = delete; 58 | MovableOnly& operator=(const MovableOnly&) = delete; 59 | using TestStruct::TestStruct; 60 | }; 61 | 62 | Vector movables; 63 | movables.PushBack(MovableOnly{ 0 }); 64 | movables.EmplaceBack(1); 65 | movables.PushBack(MovableOnly{ 2 }); 66 | TEST_TRUE(movables.Size() == 3); 67 | TEST_TRUE(movables.Capacity() >= 3); 68 | TEST_TRUE(equal(movables, expected)); 69 | 70 | struct CopyableOnly : TestStruct 71 | { 72 | using TestStruct::TestStruct; 73 | CopyableOnly(const CopyableOnly&) = default; 74 | CopyableOnly& operator=(const CopyableOnly&) = default; 75 | CopyableOnly(CopyableOnly&&) = delete; 76 | CopyableOnly& operator=(CopyableOnly&&) = delete; 77 | }; 78 | 79 | Vector copyables; 80 | CopyableOnly c(0); 81 | copyables.PushBack(c); 82 | copyables.EmplaceBack(1); 83 | c.mValue = 2; 84 | copyables.PushBack(c); 85 | TEST_TRUE(copyables.Size() == 3); 86 | TEST_TRUE(copyables.Capacity() >= 3); 87 | TEST_TRUE(equal(copyables, expected)); 88 | } 89 | 90 | { 91 | Vector test = { 0, 3 }; 92 | test.Emplace(1, 1); 93 | Vector expected = { 0, 1, 3 }; 94 | TEST_TRUE(equal(test, expected)); 95 | 96 | const int& i2 = 2; 97 | test.Insert(2, i2); 98 | expected = { 0, 1, 2, 3 }; 99 | TEST_TRUE(equal(test, expected)); 100 | 101 | test.Insert(4, 4); 102 | expected = { 0, 1, 2, 3, 4 }; 103 | TEST_TRUE(equal(test, expected)); 104 | 105 | int arr1[] = { 8, 9 }; 106 | test.Insert(5, arr1); 107 | expected = { 0, 1, 2, 3, 4, 8, 9 }; 108 | TEST_TRUE(equal(test, expected)); 109 | 110 | int arr2[] = { 5, 6, 7 }; 111 | test.Insert(5, arr2); 112 | expected = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 113 | TEST_TRUE(equal(test, expected)); 114 | 115 | test.Erase(5); 116 | expected = { 0, 1, 2, 3, 4, 6, 7, 8, 9 }; 117 | TEST_TRUE(equal(test, expected)); 118 | 119 | test.Erase(0); 120 | expected = { 1, 2, 3, 4, 6, 7, 8, 9 }; 121 | TEST_TRUE(equal(test, expected)); 122 | 123 | TEST_TRUE(test.Capacity() > test.Size()); 124 | int cap = test.Capacity(); 125 | test.ShrinkToFit(); 126 | TEST_TRUE(test.Capacity() == cap); // ShrinkToFit doesn't do anything with a heap allocator (doesn't support TryReallocate) 127 | } 128 | 129 | { 130 | Vector test; 131 | 132 | test.Resize(5, 99); 133 | 134 | Vector expected = { 99, 99, 99, 99, 99 }; 135 | TEST_TRUE(equal(test, expected)); 136 | 137 | test.Resize(1); 138 | expected = { 99 }; 139 | TEST_TRUE(equal(test, expected)); 140 | 141 | test.Resize(3); 142 | expected = { 99, 0, 0 }; 143 | TEST_TRUE(equal(test, expected)); 144 | 145 | test.Resize(5, EResizeInit::NoZeroInit); 146 | expected = { 99, 0, 0, 99, 99 }; 147 | TEST_TRUE(equal(test, expected)); 148 | } 149 | 150 | { 151 | struct CopyMoveTest 152 | { 153 | CopyMoveTest(const char* inValue) 154 | { 155 | mValue = inValue; 156 | gAssert(mValue != ""); 157 | } 158 | CopyMoveTest(const CopyMoveTest& o) 159 | { 160 | mValue = o.mValue.SubStr(0, 1); 161 | mValue += "_CopyConstructed"; 162 | } 163 | CopyMoveTest(CopyMoveTest&& o) 164 | { 165 | mValue = o.mValue.SubStr(0, 1); 166 | mValue += "_MoveConstructed"; 167 | o.mValue = "Moved"; 168 | } 169 | CopyMoveTest& operator=(const CopyMoveTest& o) 170 | { 171 | mValue = o.mValue.SubStr(0, 1); 172 | mValue += "_CopyAssigned"; 173 | return *this; 174 | } 175 | CopyMoveTest& operator=(CopyMoveTest&& o) 176 | { 177 | mValue = o.mValue.SubStr(0, 1); 178 | mValue += "_MoveAssigned"; 179 | o.mValue = "Moved"; 180 | return *this; 181 | } 182 | ~CopyMoveTest() = default; 183 | 184 | bool operator==(StringView inValue) const { return inValue == mValue; } 185 | 186 | String mValue; 187 | }; 188 | 189 | Vector test = { "0", "1", "2" }; 190 | Vector expected = { "0_CopyConstructed", "1_CopyConstructed", "2_CopyConstructed" }; 191 | TEST_TRUE(equal(test, expected)); 192 | 193 | test.Reserve(10); 194 | expected = { "0_MoveConstructed", "1_MoveConstructed", "2_MoveConstructed" }; 195 | TEST_TRUE(equal(test, expected)); 196 | 197 | test.Emplace(1, "3"); 198 | expected = { "0_MoveConstructed", "3", "1_MoveAssigned", "2_MoveConstructed" }; 199 | TEST_TRUE(equal(test, expected)); 200 | 201 | test.Emplace(4, "4"); 202 | expected = { "0_MoveConstructed", "3", "1_MoveAssigned", "2_MoveConstructed", "4" }; 203 | TEST_TRUE(equal(test, expected)); 204 | 205 | CopyMoveTest arr[] = { "5", "6", "7" }; 206 | test.Insert(4, arr); 207 | expected = { "0_MoveConstructed", "3", "1_MoveAssigned", "2_MoveConstructed", "5_CopyAssigned", "6_CopyConstructed", "7_CopyConstructed", "4_MoveConstructed" }; 208 | TEST_TRUE(equal(test, expected)); 209 | 210 | test.Erase(1, 3); 211 | expected = { "0_MoveConstructed", "5_MoveAssigned", "6_MoveAssigned", "7_MoveAssigned", "4_MoveAssigned" }; 212 | TEST_TRUE(equal(test, expected)); 213 | 214 | test.Erase(4); 215 | expected = { "0_MoveConstructed", "5_MoveAssigned", "6_MoveAssigned", "7_MoveAssigned" }; 216 | TEST_TRUE(equal(test, expected)); 217 | 218 | test.Erase(0); 219 | expected = { "5_MoveAssigned", "6_MoveAssigned", "7_MoveAssigned" }; 220 | TEST_TRUE(equal(test, expected)); 221 | } 222 | }; 223 | 224 | 225 | REGISTER_TEST("TempVector") 226 | { 227 | TEST_INIT_TEMP_MEMORY(1_KiB); 228 | 229 | TempVector test = { 1, 2, 3, 4 }; 230 | 231 | TEST_TRUE(gTempMemArena.Owns(test.Begin())); 232 | TEST_TRUE(test.Size() == 4); 233 | TEST_TRUE(test.Capacity() >= 4); 234 | 235 | int* test_begin = test.Begin(); 236 | test.Reserve(30); 237 | TEST_TRUE(test.Begin() == test_begin); // Should have resized the memory block. 238 | TEST_TRUE(test.Capacity() >= 30); 239 | TEST_TRUE(test.Size() == 4); 240 | 241 | Vector heap_vec = test; 242 | TEST_TRUE(test.Begin() != heap_vec.Begin()); 243 | TEST_TRUE(Span(test) == Span(heap_vec)); 244 | 245 | heap_vec = { 7, 8, 9 }; 246 | test = heap_vec; 247 | TEST_TRUE(test.Begin() == test_begin); 248 | TEST_TRUE(test.Begin() != heap_vec.Begin()); 249 | TEST_TRUE(Span(test) == Span(heap_vec)); 250 | 251 | test.PushBack(1); 252 | test.PopBack(); 253 | TEST_TRUE(test.Capacity() > test.Size()); 254 | test.ShrinkToFit(); 255 | TEST_TRUE(test.Capacity() == test.Size()); 256 | }; 257 | 258 | 259 | REGISTER_TEST("ArenaVector") 260 | { 261 | FixedMemArena<1_KiB> mem_arena; 262 | 263 | ArenaVector test(mem_arena); 264 | test = { 1, 2, 3, 4 }; 265 | 266 | TEST_TRUE(mem_arena.Owns(test.Begin())); 267 | TEST_TRUE(test.Size() == 4); 268 | TEST_TRUE(test.Capacity() >= 4); 269 | 270 | int* test_begin = test.Begin(); 271 | test.Reserve(30); 272 | TEST_TRUE(test.Begin() == test_begin); // Should have resized the memory block. 273 | TEST_TRUE(test.Capacity() >= 30); 274 | TEST_TRUE(test.Size() == 4); 275 | 276 | Vector heap_vec = test; 277 | TEST_TRUE(test.Begin() != heap_vec.Begin()); 278 | TEST_TRUE(Span(test) == Span(heap_vec)); 279 | 280 | heap_vec = { 7, 8, 9 }; 281 | test = heap_vec; 282 | TEST_TRUE(test.Begin() == test_begin); 283 | TEST_TRUE(test.Begin() != heap_vec.Begin()); 284 | TEST_TRUE(Span(test) == heap_vec); 285 | 286 | test.PushBack(1); 287 | test.PopBack(); 288 | TEST_TRUE(test.Capacity() > test.Size()); 289 | test.ShrinkToFit(); 290 | TEST_TRUE(test.Capacity() == test.Size()); 291 | 292 | FixedMemArena<128_B> mem_arena2; 293 | ArenaVector test2(mem_arena2); 294 | 295 | // Copy to a different arena 296 | test2 = test; 297 | TEST_TRUE(test2 == Span(test)); 298 | TEST_TRUE(mem_arena2.Owns(test2.Begin())); 299 | TEST_TRUE(test2.GetAllocator().GetArena() == &mem_arena2); 300 | 301 | // Move also moves the arena 302 | test2 = gMove(test); 303 | TEST_TRUE(Span(test2) == heap_vec); 304 | TEST_TRUE(test.Empty()); 305 | TEST_TRUE(mem_arena.Owns(test2.Begin())); 306 | TEST_TRUE(test2.GetAllocator().GetArena() == &mem_arena); 307 | TEST_TRUE(test.GetAllocator().GetArena() == nullptr); 308 | }; 309 | 310 | 311 | REGISTER_TEST("VMemVector") 312 | { 313 | VMemVector test; 314 | test = { 1, 2, 3, 4 }; 315 | 316 | TEST_TRUE(test.Size() == 4); 317 | TEST_TRUE(test.Capacity() >= 4); 318 | 319 | int* test_begin = test.Begin(); 320 | test.Reserve(30); 321 | TEST_TRUE(test.Begin() == test_begin); // Should have resized the memory block. 322 | TEST_TRUE(test.Capacity() >= 30); 323 | TEST_TRUE(test.Size() == 4); 324 | 325 | Vector heap_vec = test; 326 | TEST_TRUE(test.Begin() != heap_vec.Begin()); 327 | TEST_TRUE(Span(test) == Span(heap_vec)); 328 | 329 | heap_vec = { 7, 8, 9 }; 330 | test = heap_vec; 331 | TEST_TRUE(test.Begin() == test_begin); 332 | TEST_TRUE(test.Begin() != heap_vec.Begin()); 333 | TEST_TRUE(Span(test) == heap_vec); 334 | 335 | test.PushBack(1); 336 | test.PopBack(); 337 | TEST_TRUE(test.Capacity() > test.Size()); 338 | test.ShrinkToFit(); 339 | TEST_TRUE(test.Capacity() == test.Size()); 340 | 341 | VMemVector test2(VMemVector::Allocator(8_KiB, 4_KiB)); 342 | 343 | test2 = test; 344 | TEST_TRUE(test2 == Span(test)); 345 | 346 | // This should cause a new page to be committed. 347 | test2.Resize(5_KiB / sizeof(test[0]), EResizeInit::NoZeroInit); 348 | test2.PushBack(1); // Make sure writing to it doesn't crash. 349 | 350 | test2 = gMove(test); 351 | TEST_TRUE(Span(test2) == heap_vec); 352 | TEST_TRUE(test.Empty()); 353 | }; 354 | 355 | 356 | REGISTER_TEST("FixedVector") 357 | { 358 | FixedVector test; 359 | test = { 1, 2, 3, 4, 5 }; 360 | 361 | TEST_TRUE(test.Size() == 5); 362 | TEST_TRUE(test.Capacity() == 5); 363 | TEST_TRUE(test.MaxSize() == 6); 364 | 365 | test.PushBack(6); // Should not grow above max size 366 | TEST_TRUE(test.Capacity() == 6); 367 | 368 | int* test_begin = test.Begin(); 369 | 370 | Vector heap_vec = test; 371 | TEST_TRUE(test.Begin() != heap_vec.Begin()); 372 | TEST_TRUE(Span(test) == Span(heap_vec)); 373 | 374 | heap_vec = { 7, 8, 9 }; 375 | test = heap_vec; 376 | TEST_TRUE(test.Begin() == test_begin); 377 | TEST_TRUE(test.Begin() != heap_vec.Begin()); 378 | TEST_TRUE(Span(test) == heap_vec); 379 | 380 | test.PushBack(1); 381 | test.PopBack(); 382 | TEST_TRUE(test.Capacity() > test.Size()); 383 | test.ShrinkToFit(); 384 | TEST_TRUE(test.Capacity() == test.Size()); 385 | 386 | FixedVector test2; 387 | 388 | // Copy to a different vector 389 | test2 = test; 390 | TEST_TRUE(test2 == Span(test)); 391 | TEST_TRUE(test2.Data() != test.Data()); 392 | 393 | // Move also copies since there's no move operator 394 | test2 = gMove(test); 395 | TEST_TRUE(Span(test2) == heap_vec); 396 | TEST_TRUE(!test.Empty()); 397 | TEST_TRUE(test2.Data() != test.Data()); 398 | }; -------------------------------------------------------------------------------- /Bedrock/MemoryArena.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace Details 8 | { 9 | struct FreeBlock 10 | { 11 | int mEndOffset = 0; 12 | int mSize = 0; 13 | int BeginOffset() const { return mEndOffset - mSize; } 14 | }; 15 | 16 | template 17 | struct PendingFreeArray 18 | { 19 | no_inline void AddPendingFree(FreeBlock inFreeBlock); 20 | no_inline void TryRemovePendingFree(int& ioCurrentOffset); 21 | force_inline int GetNumPendingFree() const { return mCount; } 22 | 23 | int mCount = 0; 24 | FreeBlock mBlocks[taSize]; // Sorted in descending order. 25 | }; 26 | 27 | // Specialization that stores nothing. 28 | template <> struct PendingFreeArray<0> 29 | { 30 | force_inline static void AddPendingFree(FreeBlock inFreeBlock) { CRASH; } 31 | force_inline static void TryRemovePendingFree(int& ioCurrentOffset) {} 32 | force_inline static int GetNumPendingFree() { return 0; } 33 | }; 34 | } 35 | 36 | 37 | constexpr int cDefaultMaxPendingFrees = 16; 38 | 39 | 40 | // Simple linear allocator. 41 | // Allocations must be freed in order in general, but a small number of out of order frees is optionally supported (see taMaxPendingFrees). 42 | // Deals only in bytes. For typed allocators to use with containers, see Allocator.h 43 | template 44 | struct MemArena : Details::PendingFreeArray 45 | { 46 | using Base = Details::PendingFreeArray; 47 | 48 | static constexpr int cAlignment = 16; 49 | 50 | // Default 51 | MemArena() = default; 52 | ~MemArena() { gAssert(GetAllocatedSize() == 0); } 53 | 54 | // Not copyable 55 | MemArena(const MemArena&) = delete; 56 | MemArena& operator=(const MemArena&) = delete; 57 | 58 | // Move 59 | MemArena(MemArena&& ioOther) { operator=((MemArena&&)ioOther); } 60 | MemArena& operator=(MemArena&& ioOther) 61 | { 62 | gAssert(GetAllocatedSize() == 0); 63 | 64 | mBeginPtr = ioOther.mBeginPtr; 65 | mEndOffset = ioOther.mEndOffset; 66 | mCurrentOffset = ioOther.mCurrentOffset; 67 | Base::operator=((Base&&)ioOther); 68 | 69 | ioOther.mBeginPtr = nullptr; 70 | ioOther.mEndOffset = 0; 71 | ioOther.mCurrentOffset = 0; 72 | ioOther.Base::operator=({}); 73 | 74 | return *this; 75 | } 76 | 77 | // Initialize this arena with a memory block. 78 | MemArena(MemBlock inMemory) 79 | { 80 | gAssert(mBeginPtr == nullptr); // Already initialized. 81 | gAssert(((uint64)inMemory.mPtr % cAlignment) == 0); // Pointer should be aligned. 82 | gAssert((inMemory.mSize % cAlignment) == 0); // Size should be aligned. 83 | 84 | mBeginPtr = inMemory.mPtr; 85 | mEndOffset = (int)inMemory.mSize; 86 | mCurrentOffset = 0; 87 | } 88 | 89 | // Get this arena's entire memory block. 90 | MemBlock GetMemBlock() const 91 | { 92 | return { mBeginPtr, mEndOffset }; 93 | } 94 | 95 | // Allocate memory. 96 | MemBlock Alloc(int inSize) 97 | { 98 | gAssert(mBeginPtr != nullptr); // Need to initialize with a MemBlock first. 99 | 100 | int aligned_size = (int)gAlignUp(inSize, cAlignment); 101 | int current_offset = mCurrentOffset; 102 | 103 | if (current_offset + aligned_size > mEndOffset) 104 | return {}; // Allocation failed. 105 | 106 | mCurrentOffset += aligned_size; 107 | 108 | return { mBeginPtr + current_offset, inSize }; 109 | } 110 | 111 | // Free memory. inMemory should be the last allocation, or the arena should support enough out-of-order frees. 112 | void Free(MemBlock inMemory) 113 | { 114 | gAssert(inMemory.mPtr != nullptr); 115 | gAssert(inMemory.mSize > 0); 116 | gAssert(Owns(inMemory.mPtr)); 117 | 118 | int aligned_size = (int)gAlignUp(inMemory.mSize, cAlignment); 119 | int end_offset = (int)(inMemory.mPtr + aligned_size - mBeginPtr); 120 | 121 | // If it's the last alloc, free immediately. 122 | if (end_offset == mCurrentOffset) [[likely]] 123 | { 124 | mCurrentOffset -= aligned_size; 125 | 126 | // If there are frees pending because they were made out of order, check if they can be freed now. 127 | if (GetNumPendingFree() > 0) [[unlikely]] 128 | TryRemovePendingFree(mCurrentOffset); 129 | } 130 | else 131 | { 132 | // Otherwise add it to the list of pending frees. 133 | AddPendingFree({ end_offset, aligned_size }); 134 | } 135 | } 136 | 137 | // Try resizing ioMemory. Return true on success. Can fail if ioMemory isn't the last block or not enough free memory for inNewSize. 138 | bool TryRealloc(MemBlock& ioMemory, int inNewSize) 139 | { 140 | gAssert(Owns(ioMemory.mPtr)); 141 | 142 | if (!IsLastAlloc(ioMemory)) [[unlikely]] 143 | return false; 144 | 145 | int aligned_new_size = (int)gAlignUp(inNewSize, cAlignment); 146 | int new_current_offset = (int)(ioMemory.mPtr + aligned_new_size - mBeginPtr); 147 | 148 | if (new_current_offset > mEndOffset) [[unlikely]] 149 | return false; // Wouldn't fit. 150 | 151 | mCurrentOffset = new_current_offset; 152 | ioMemory.mSize = inNewSize; 153 | 154 | return true; 155 | } 156 | 157 | // Return true if inMemoryPtr is inside this arena. 158 | bool Owns(const void* inMemoryPtr) const 159 | { 160 | return ((const uint8*)inMemoryPtr >= mBeginPtr && (const uint8*)inMemoryPtr < (mBeginPtr + mEndOffset)); 161 | } 162 | 163 | // Return true if inMemory is the last allocation made in this arena. 164 | bool IsLastAlloc(MemBlock inMemory) const 165 | { 166 | return (inMemory.mPtr + gAlignUp(inMemory.mSize, cAlignment)) == (mBeginPtr + mCurrentOffset); 167 | } 168 | 169 | // Return the amount of memory currently allocated. 170 | int GetAllocatedSize() const 171 | { 172 | return mCurrentOffset; 173 | } 174 | 175 | using Base::GetNumPendingFree; 176 | 177 | protected: 178 | uint8* mBeginPtr = nullptr; 179 | int mEndOffset = 0; 180 | int mCurrentOffset = 0; 181 | 182 | using Base::AddPendingFree; 183 | using Base::TryRemovePendingFree; 184 | }; 185 | 186 | 187 | // Version of MemArena that embeds a fixed-size buffer and allocates from it. 188 | template 189 | struct FixedMemArena : MemArena 190 | { 191 | using Base = MemArena; 192 | 193 | FixedMemArena() : Base({ mBuffer, (int64)taSize }) {} 194 | ~FixedMemArena() { gAssert(Base::GetAllocatedSize() == 0); } 195 | 196 | // Not movable since data is embedded. 197 | FixedMemArena(FixedMemArena&&) = delete; 198 | FixedMemArena& operator=(FixedMemArena&&) = delete; 199 | 200 | private: 201 | alignas(Base::cAlignment) uint8 mBuffer[taSize]; 202 | }; 203 | 204 | 205 | // Version of MemArena that allocates virtual memory as backing. Can grow. 206 | template 207 | struct VMemArena : MemArena 208 | { 209 | using Base = MemArena; 210 | 211 | static constexpr int64 cDefaultReservedSize = 100_MiB; // By default the arena will reserve that much virtual memory. 212 | static constexpr int64 cDefaultCommitSize = 64_KiB; // By default the arena will commit that much virtual memory every time it grows. 213 | 214 | VMemArena() = default; 215 | ~VMemArena() { FreeReserved(); } 216 | 217 | // Initialize this arena with reserved memory (but no committed memory yet). 218 | VMemArena(int64 inReservedSize, int64 inCommitIncreaseSize) 219 | { 220 | // Replace parameters by defaults if necessary. 221 | if (inReservedSize <= 0) 222 | inReservedSize = cDefaultReservedSize; 223 | if (inCommitIncreaseSize <= 0) 224 | inCommitIncreaseSize = cDefaultCommitSize; 225 | 226 | mCommitIncreaseSize = (int)gAlignUp(inCommitIncreaseSize, gVMemCommitGranularity()); 227 | 228 | // Reserve the memory. 229 | MemBlock reserved_mem = gVMemReserve(inReservedSize); 230 | mEndReservedOffset = (int)reserved_mem.mSize; 231 | 232 | // Initialize the parent MemArena with a zero-sized block (no memory is committed yet). 233 | Base::operator=(MemBlock{ reserved_mem.mPtr, 0 }); 234 | } 235 | 236 | VMemArena(VMemArena&& ioOther) { operator=((VMemArena&&)ioOther); } 237 | VMemArena& operator=(VMemArena&& ioOther) 238 | { 239 | FreeReserved(); 240 | 241 | Base::operator=((Base&&)ioOther); 242 | 243 | mCommitIncreaseSize = ioOther.mCommitIncreaseSize; 244 | mEndReservedOffset = ioOther.mEndReservedOffset; 245 | ioOther.mEndReservedOffset = 0; 246 | ioOther.mCommitIncreaseSize = 0; 247 | 248 | return *this; 249 | } 250 | 251 | MemBlock Alloc(int inSize) 252 | { 253 | // If the arena wasn't initialized yet, do it now (with default values). 254 | // It's better to do it lazily than reserving virtual memory in every container default constructor. 255 | if (mBeginPtr == nullptr) [[unlikely]] 256 | *this = VMemArena(cDefaultReservedSize, cDefaultCommitSize); 257 | 258 | int aligned_size = (int)gAlignUp(inSize, cAlignment); 259 | int new_current_offset = mCurrentOffset + aligned_size; 260 | 261 | // Check if we need to commit more memory. 262 | if (new_current_offset > mEndOffset) [[unlikely]] 263 | CommitMore(new_current_offset); 264 | 265 | return Base::Alloc(inSize); 266 | } 267 | 268 | bool TryRealloc(MemBlock& ioMemory, int inNewSize) 269 | { 270 | if (!IsLastAlloc(ioMemory)) [[unlikely]] 271 | return false; 272 | 273 | int aligned_new_size = (int)gAlignUp(inNewSize, cAlignment); 274 | int new_current_offset = (int)(ioMemory.mPtr + aligned_new_size - mBeginPtr); 275 | 276 | // Check if we need to commit more memory. 277 | if (new_current_offset > mEndOffset) [[unlikely]] 278 | CommitMore(new_current_offset); 279 | 280 | return Base::TryRealloc(ioMemory, inNewSize); 281 | } 282 | 283 | int GetReservedSize() const { return mEndReservedOffset; } 284 | 285 | using Base::IsLastAlloc; 286 | using Base::cAlignment; 287 | 288 | private: 289 | void CommitMore(int inNewEndOffset); 290 | void FreeReserved(); 291 | 292 | using Base::mBeginPtr; 293 | using Base::mCurrentOffset; 294 | using Base::mEndOffset; 295 | 296 | int mEndReservedOffset = 0; 297 | int mCommitIncreaseSize = 64_KiB; 298 | }; 299 | 300 | 301 | 302 | template 303 | void Details::PendingFreeArray::AddPendingFree(FreeBlock inFreeBlock) 304 | { 305 | for (int i = 0; i < mCount; i++) 306 | { 307 | if (inFreeBlock.mEndOffset < mBlocks[i].BeginOffset()) 308 | { 309 | // Inserting before. 310 | if (mCount == taSize) [[unlikely]] 311 | gCrash("MemArena: too many out of order frees"); 312 | 313 | // Move all the blocks towards the back to make room. 314 | gMemMove(&mBlocks[i + 1], &mBlocks[i], sizeof(FreeBlock) * (mCount - i)); 315 | 316 | // Place the new block. 317 | mBlocks[i] = inFreeBlock; 318 | mCount++; 319 | 320 | return; 321 | } 322 | 323 | if (inFreeBlock.mEndOffset == mBlocks[i].BeginOffset()) 324 | { 325 | // Inserting just before, merge instead. 326 | mBlocks[i].mSize += inFreeBlock.mSize; 327 | 328 | return; 329 | } 330 | 331 | if (inFreeBlock.BeginOffset() == mBlocks[i].mEndOffset) 332 | { 333 | // Inserting just after, merge instead. 334 | mBlocks[i].mEndOffset = inFreeBlock.mEndOffset; 335 | mBlocks[i].mSize += inFreeBlock.mSize; 336 | 337 | // Check if the next block can be merged as well now. 338 | if ((i + 1) < mCount) 339 | { 340 | if (mBlocks[i].mEndOffset == mBlocks[i + 1].BeginOffset()) 341 | { 342 | // Merge the next block. 343 | mBlocks[i].mEndOffset = mBlocks[i + 1].mEndOffset; 344 | mBlocks[i].mSize += mBlocks[i + 1].mSize; 345 | 346 | // Move all the following blocks towards the front to fill the gap. 347 | gMemMove(&mBlocks[i + 1], &mBlocks[i + 2], sizeof(FreeBlock) * (mCount - 2 - i)); 348 | mCount--; 349 | } 350 | } 351 | 352 | return; 353 | } 354 | } 355 | 356 | // Otherwise add it at the back of the list. 357 | if (mCount == taSize) 358 | gCrash("MemArena: too many out of order frees"); 359 | 360 | mBlocks[mCount] = inFreeBlock; 361 | mCount++; 362 | 363 | } 364 | 365 | 366 | template 367 | void Details::PendingFreeArray::TryRemovePendingFree(int& ioCurrentOffset) 368 | { 369 | if (mCount == 0) 370 | return; 371 | 372 | // Pending blocks are sorted and coalesced, so we only need to check the last one. 373 | if (mBlocks[mCount - 1].mEndOffset == ioCurrentOffset) 374 | { 375 | // Free it. 376 | ioCurrentOffset -= mBlocks[mCount - 1].mSize; 377 | mCount--; 378 | } 379 | 380 | } 381 | 382 | 383 | template 384 | void VMemArena::CommitMore(int inNewEndOffset) 385 | { 386 | gAssert(inNewEndOffset > mEndOffset); 387 | 388 | int64 commit_size = gMax(mCommitIncreaseSize, (inNewEndOffset - mEndOffset)); 389 | MemBlock committed_mem = gVMemCommit({ mBeginPtr + mEndOffset, commit_size }); 390 | 391 | mEndOffset = (int)(committed_mem.mPtr + committed_mem.mSize - mBeginPtr); 392 | } 393 | 394 | template 395 | void VMemArena::FreeReserved() 396 | { 397 | if (mBeginPtr != nullptr) 398 | gVMemFree({ mBeginPtr, mEndReservedOffset }); 399 | } 400 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | --------------------------------------------------------------------------------