From e2f024d8d58a4c9de91f19fba25ae0e392faa4ca Mon Sep 17 00:00:00 2001 From: muit Date: Sun, 2 Jun 2024 22:24:15 +0200 Subject: [PATCH 01/14] Added string conversion tests --- Include/Pipe/Core/String.h | 7 +++++++ Tests/Core/String.spec.cpp | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/Include/Pipe/Core/String.h b/Include/Pipe/Core/String.h index 6735c719..97ff7026 100644 --- a/Include/Pipe/Core/String.h +++ b/Include/Pipe/Core/String.h @@ -151,6 +151,13 @@ namespace p ConvertTo(source, dest); return Move(dest); } + template + inline ToStringType Convert(const TString& source) + { + ToStringType dest; + ConvertTo(TStringView{source}, dest); + return Move(dest); + } }; // namespace Strings diff --git a/Tests/Core/String.spec.cpp b/Tests/Core/String.spec.cpp index 710aa6e6..a7c99a73 100644 --- a/Tests/Core/String.spec.cpp +++ b/Tests/Core/String.spec.cpp @@ -82,6 +82,29 @@ go_bandit([]() { AssertThat(Strings::ToSentenceCase("papa3"), Equals("Papa 3")); AssertThat(Strings::ToSentenceCase("MisterPotato"), Equals("Mister Potato")); }); + + it("Convert u16 to u8", [&]() { + TString utf16string{0x41, 0x0448, 0x65e5, 0xd834, 0xdd1e}; + TString u = Strings::Convert>(utf16string); + AssertThat(u.size(), Equals(10)); + }); + it("Convert u8 to u16", [&]() { + TString utf8_with_surrogates = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e"; + TString utf16result = Strings::Convert>(utf8_with_surrogates); + AssertThat(utf16result.size(), Equals(4)); + AssertThat(utf16result[2], Equals(0xd834)); + AssertThat(utf16result[3], Equals(0xdd1e)); + }); + it("Convert u32 to u8", [&]() { + TString utf32string = {0x448, 0x65E5, 0x10346}; + TString utf8result = Strings::Convert>(utf32string); + AssertThat(utf8result.size(), Equals(9)); + }); + it("Convert u8 to u32", [&]() { + TString twochars = "\xe6\x97\xa5\xd1\x88"; + TString utf32result = Strings::Convert>(twochars); + AssertThat(utf32result.size(), Equals(2)); + }); }); }); }); From e952715e1c83df34b3c3ae7fb7e1a8a299014d1e Mon Sep 17 00:00:00 2001 From: muit Date: Sun, 2 Jun 2024 22:24:40 +0200 Subject: [PATCH 02/14] Moved memory stats to heap arena --- .vscode/launch.json | 13 ++++++++++++- Include/Pipe/Memory/Alloc.h | 1 - Include/Pipe/Memory/HeapArena.h | 15 +++++++++++++-- Src/Memory/Alloc.cpp | 28 ++-------------------------- Src/PipeReflect.cpp | 6 +++--- 5 files changed, 30 insertions(+), 33 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 4d790919..74510c9c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,18 @@ "name": "Test", "type": "cppvsdbg", "request": "launch", - "program": "${workspaceFolder}/Build/Bin/RiftCoreTests.exe", + "program": "${workspaceFolder}/Build/Bin/PipeTests.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false + }, + { + "name": "Test (Linux)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/Build/Bin/PipeTests", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", diff --git a/Include/Pipe/Memory/Alloc.h b/Include/Pipe/Memory/Alloc.h index 186d6477..1c2c3360 100644 --- a/Include/Pipe/Memory/Alloc.h +++ b/Include/Pipe/Memory/Alloc.h @@ -23,7 +23,6 @@ namespace p PIPE_API class HeapArena& GetHeapArena(); PIPE_API Arena& GetCurrentArena(); PIPE_API void SetCurrentArena(Arena& arena); - PIPE_API MemoryStats* GetHeapStats(); // Arena allocation functions (Find current arena) PIPE_API void* Alloc(sizet size); diff --git a/Include/Pipe/Memory/HeapArena.h b/Include/Pipe/Memory/HeapArena.h index 4db87f6b..7b11341c 100644 --- a/Include/Pipe/Memory/HeapArena.h +++ b/Include/Pipe/Memory/HeapArena.h @@ -4,11 +4,15 @@ #include "Pipe/Memory/Alloc.h" #include "Pipe/Memory/Arena.h" +#include "Pipe/Memory/MemoryStats.h" + namespace p { class PIPE_API HeapArena : public Arena { + MemoryStats stats; + public: HeapArena() { @@ -18,11 +22,15 @@ namespace p void* Alloc(const sizet size) { - return p::HeapAlloc(size); + void* ptr = p::HeapAlloc(size); + stats.Add(ptr, size); + return ptr; } void* Alloc(const sizet size, const sizet align) { - return p::HeapAlloc(size, align); + void* ptr = p::HeapAlloc(size, align); + stats.Add(ptr, size); + return ptr; } bool Realloc(void* ptr, const sizet ptrSize, const sizet size) { @@ -30,7 +38,10 @@ namespace p } void Free(void* ptr, sizet size) { + stats.Remove(ptr); p::HeapFree(ptr); } + + const MemoryStats& GetStats() const { return stats; } }; } // namespace p diff --git a/Src/Memory/Alloc.cpp b/Src/Memory/Alloc.cpp index e9543c93..226c2a73 100644 --- a/Src/Memory/Alloc.cpp +++ b/Src/Memory/Alloc.cpp @@ -16,17 +16,12 @@ namespace p void InitializeMemory() { - GetHeapStats(); GetHeapArena(); } void* HeapAlloc(sizet size) { - void* const ptr = malloc(size); -#if P_DEBUG - GetHeapStats()->Add(ptr, size); -#endif - return ptr; + return malloc(size); } void* HeapAlloc(sizet size, sizet align) @@ -36,30 +31,17 @@ namespace p void* const ptr = malloc(size); #else void* const ptr = aligned_alloc(align, size); -#endif -#if P_DEBUG - GetHeapStats()->Add(ptr, size); #endif return ptr; } void* HeapRealloc(void* ptr, sizet size) { -#if P_DEBUG - GetHeapStats()->Remove(ptr); -#endif - ptr = realloc(ptr, size); -#if P_DEBUG - GetHeapStats()->Add(ptr, size); -#endif - return ptr; + return realloc(ptr, size); } void HeapFree(void* ptr) { -#if P_DEBUG - GetHeapStats()->Remove(ptr); -#endif free(ptr); } @@ -80,12 +62,6 @@ namespace p currentArena = &arena; } - MemoryStats* GetHeapStats() - { - static MemoryStats heapStats; - return &heapStats; - } - void* Alloc(Arena& arena, sizet size) { diff --git a/Src/PipeReflect.cpp b/Src/PipeReflect.cpp index 80a50b30..1a08e8cd 100644 --- a/Src/PipeReflect.cpp +++ b/Src/PipeReflect.cpp @@ -54,7 +54,7 @@ namespace p static TypeRegistry registry; return registry; } - TArray& GetRefectInitCallacks() + TArray& GetRefectInitCallbacks() { static TArray onInitCallbacks{GetHeapArena()}; return onInitCallbacks; @@ -154,7 +154,7 @@ namespace p return false; } - for (auto callback : GetRefectInitCallacks()) + for (auto callback : GetRefectInitCallbacks()) { if (callback) { @@ -169,7 +169,7 @@ namespace p { if (callback) { - GetRefectInitCallacks().Add(callback); + GetRefectInitCallbacks().Add(callback); } } From b55306210cc2e4aafebd7f79a71223ca8b2b9f42 Mon Sep 17 00:00:00 2001 From: muit Date: Sun, 2 Jun 2024 23:43:24 +0200 Subject: [PATCH 03/14] Use memory stats in arenas, moved some arena stats into them --- Include/Pipe/Memory/Alloc.h | 1 - Include/Pipe/Memory/Arena.h | 7 +-- Include/Pipe/Memory/HeapArena.h | 6 ++- Include/Pipe/Memory/MemoryStats.h | 7 ++- Include/Pipe/Memory/MonoLinearArena.h | 16 ++++--- Include/Pipe/Memory/MultiLinearArena.h | 6 --- Src/Memory/Alloc.cpp | 1 - Src/Memory/MemoryStats.cpp | 60 +++++++++++++++----------- Src/Memory/MonoLinearArena.cpp | 15 +++++-- Tests/CMakeLists.txt | 2 +- Tests/Memory/MonoLinearArena.spec.cpp | 34 +++++++++------ 11 files changed, 92 insertions(+), 63 deletions(-) diff --git a/Include/Pipe/Memory/Alloc.h b/Include/Pipe/Memory/Alloc.h index 1c2c3360..e3cebdab 100644 --- a/Include/Pipe/Memory/Alloc.h +++ b/Include/Pipe/Memory/Alloc.h @@ -9,7 +9,6 @@ namespace p { class Arena; - struct MemoryStats; void InitializeMemory(); diff --git a/Include/Pipe/Memory/Arena.h b/Include/Pipe/Memory/Arena.h index 8124dda2..c0c20d0e 100644 --- a/Include/Pipe/Memory/Arena.h +++ b/Include/Pipe/Memory/Arena.h @@ -117,16 +117,13 @@ namespace p doFree(this, ptr, size); } - - virtual sizet GetUsedMemory() const - { - return 0; - } virtual sizet GetAvailableMemory() const { return 0; } virtual void GetBlocks(TInlineArray& outBlocks) const {} + + virtual const struct MemoryStats* GetStats() const { return nullptr; } }; class PIPE_API ChildArena : public Arena diff --git a/Include/Pipe/Memory/HeapArena.h b/Include/Pipe/Memory/HeapArena.h index 7b11341c..6619c2a3 100644 --- a/Include/Pipe/Memory/HeapArena.h +++ b/Include/Pipe/Memory/HeapArena.h @@ -11,11 +11,13 @@ namespace p { class PIPE_API HeapArena : public Arena { + private: MemoryStats stats; public: HeapArena() { + stats.name = "Heap Arena"; Interface(); } ~HeapArena() override = default; @@ -38,10 +40,10 @@ namespace p } void Free(void* ptr, sizet size) { - stats.Remove(ptr); + stats.Remove(ptr, size); p::HeapFree(ptr); } - const MemoryStats& GetStats() const { return stats; } + const MemoryStats* GetStats() const override { return &stats; } }; } // namespace p diff --git a/Include/Pipe/Memory/MemoryStats.h b/Include/Pipe/Memory/MemoryStats.h index a80cf877..594d654b 100644 --- a/Include/Pipe/Memory/MemoryStats.h +++ b/Include/Pipe/Memory/MemoryStats.h @@ -42,7 +42,10 @@ namespace p struct PIPE_API MemoryStats { + const char* name = "Arena"; + sizet used = 0; + sizet available = 0; mutable std::shared_mutex mutex; TArray allocations; #if P_ENABLE_ALLOCATION_STACKS @@ -50,11 +53,13 @@ namespace p #endif TArray freedAllocations; + MemoryStats(); ~MemoryStats(); void Add(void* ptr, sizet size); - void Remove(void* ptr); + void Remove(void* ptr, sizet size); + void Release(); private: void PrintAllocationError( diff --git a/Include/Pipe/Memory/MonoLinearArena.h b/Include/Pipe/Memory/MonoLinearArena.h index d22ae1b9..7b716e76 100644 --- a/Include/Pipe/Memory/MonoLinearArena.h +++ b/Include/Pipe/Memory/MonoLinearArena.h @@ -10,6 +10,8 @@ #include "PipeArrays.h" #include "PipeMath.h" +#include "Pipe/Memory/MemoryStats.h" + namespace p { @@ -21,17 +23,21 @@ namespace p */ class PIPE_API MonoLinearArena : public ChildArena { +private: + MemoryStats stats; + protected: void* insert = nullptr; sizet count = 0; Memory::Block block{}; bool selfAllocated = false; - + public: MonoLinearArena(Memory::Block externalBlock, Arena& parentArena = GetCurrentArena()) : ChildArena(&parentArena), insert{externalBlock.data}, block{Move(externalBlock)} { + stats.name = "Mono Linear Arena"; Interface(); } MonoLinearArena(const sizet blockSize = Memory::MB, Arena& parentArena = GetCurrentArena()) @@ -40,6 +46,7 @@ namespace p , block{insert, blockSize} , selfAllocated{true} { + stats.name = "Mono Linear Arena"; Interface(); } ~MonoLinearArena() override @@ -61,11 +68,6 @@ namespace p void Release(bool keepIfSelfAllocated = true); - - sizet GetUsedMemory() const override - { - return (u8*)insert - (u8*)block.data; - } sizet GetAvailableMemory() const override { return block.size; @@ -74,6 +76,8 @@ namespace p { outBlocks.Add(block); } + + const MemoryStats* GetStats() const override { return &stats; } }; // TMonoLinearArena works like a MonoLinearArena but providing a block on the stack as the block diff --git a/Include/Pipe/Memory/MultiLinearArena.h b/Include/Pipe/Memory/MultiLinearArena.h index 252edefb..5612ff49 100644 --- a/Include/Pipe/Memory/MultiLinearArena.h +++ b/Include/Pipe/Memory/MultiLinearArena.h @@ -115,11 +115,5 @@ namespace p void Release(); void Grow(sizet size, sizet align = 0); - - sizet GetUsedMemory() const override - { - P_NotImplemented; - return 0; - } }; } // namespace p diff --git a/Src/Memory/Alloc.cpp b/Src/Memory/Alloc.cpp index 226c2a73..4976ccc7 100644 --- a/Src/Memory/Alloc.cpp +++ b/Src/Memory/Alloc.cpp @@ -4,7 +4,6 @@ #include "Pipe/Memory/Arena.h" #include "Pipe/Memory/HeapArena.h" -#include "Pipe/Memory/MemoryStats.h" #include #include diff --git a/Src/Memory/MemoryStats.cpp b/Src/Memory/MemoryStats.cpp index 391301a1..6a0d91a2 100644 --- a/Src/Memory/MemoryStats.cpp +++ b/Src/Memory/MemoryStats.cpp @@ -58,28 +58,7 @@ namespace p MemoryStats::~MemoryStats() { - if (allocations.Size() > 0) - { - TString errorMsg; - Strings::FormatTo( - errorMsg, "MEMORY LEAKS! {} allocations were not freed!", allocations.Size()); - - const auto shown = Min(64, allocations.Size()); - for (i32 i = 0; i < shown; ++i) - { - PrintAllocationError("", &allocations[i], nullptr); -#if P_ENABLE_ALLOCATION_STACKS - PrintAllocationError("", &allocations[i], &stacks[i]); -#endif - } - - if (shown < allocations.Size()) - { - Strings::FormatTo( - errorMsg, "\n...\n{} more not shown.", allocations.Size() - shown); - } - std::puts(errorMsg.data()); - } + Release(); } void MemoryStats::PrintAllocationError( @@ -123,19 +102,21 @@ namespace p #endif } - void MemoryStats::Remove(void* ptr) + void MemoryStats::Remove(void* ptr, sizet size) { if (!ptr) { return; } + used -= size; + std::unique_lock lock{mutex}; const i32 index = allocations.FindSorted(ptr, SortLessAllocationStats{}); if (index != NO_INDEX) { AllocationStats& allocation = allocations[index]; - used -= allocation.size; + P_CheckMsg(size == allocation.size, "Freed an allocation with a different size to which it got allocated with."); freedAllocations.AddSorted(Move(allocation), SortLessAllocationStats{}); allocations.RemoveAt(index); #if P_ENABLE_ALLOCATION_STACKS @@ -154,4 +135,35 @@ namespace p } } } + + void MemoryStats::Release() + { + if (allocations.Size() > 0) + { + TString errorMsg; + Strings::FormatTo( + errorMsg, "{}: {} allocations were not freed!", name, allocations.Size()); + + const auto shown = Min(64, allocations.Size()); + for (i32 i = 0; i < shown; ++i) + { + PrintAllocationError("", &allocations[i], nullptr); +#if P_ENABLE_ALLOCATION_STACKS + PrintAllocationError("", &allocations[i], &stacks[i]); +#endif + } + + if (shown < allocations.Size()) + { + Strings::FormatTo( + errorMsg, "\n...\n{} more not shown.", allocations.Size() - shown); + } + std::puts(errorMsg.data()); + } + allocations.Clear(); +#if P_ENABLE_ALLOCATION_STACKS + stacks.Clear(); +#endif // P_ENABLE_ALLOCATION_STACKS + used = 0; + } } // namespace p diff --git a/Src/Memory/MonoLinearArena.cpp b/Src/Memory/MonoLinearArena.cpp index 26adb5d7..1b0feb17 100644 --- a/Src/Memory/MonoLinearArena.cpp +++ b/Src/Memory/MonoLinearArena.cpp @@ -11,20 +11,26 @@ namespace p void* MonoLinearArena::Alloc(sizet size, sizet align) { u8* const allocEnd = (u8*)insert + size + GetAlignmentPadding(insert, align); + void* ptr; // Not enough space in current block? if (allocEnd <= block.End()) [[likely]] { insert = allocEnd; ++count; - return allocEnd - size; // Fast-path + ptr = allocEnd - size; // Fast-path } - - // Allocation doesn't fit. Allocate in parent arena - return GetParentArena().Alloc(size, align); + else + { + // Allocation doesn't fit. Allocate in parent arena + ptr = GetParentArena().Alloc(size, align); + } + stats.Add(ptr, size); + return ptr; } void MonoLinearArena::Free(void* ptr, sizet size) { + stats.Remove(ptr, size); if (ptr >= block.data && ptr < block.End()) [[likely]] { --count; @@ -41,6 +47,7 @@ namespace p void MonoLinearArena::Release(bool keepIfSelfAllocated) { + stats.Release(); insert = block.data; count = 0; if (selfAllocated && !keepIfSelfAllocated) diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 7e4bd764..1887a82b 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -11,4 +11,4 @@ pipe_target_shared_output_directory(PipeTests) target_link_libraries(PipeTests PUBLIC Pipe Bandit) pipe_add_sanitizers(PipeTests) -add_test(NAME PipeTests COMMAND $) \ No newline at end of file +add_test(NAME PipeTests COMMAND $ --reporter=spec) \ No newline at end of file diff --git a/Tests/Memory/MonoLinearArena.spec.cpp b/Tests/Memory/MonoLinearArena.spec.cpp index 7e5e7902..2373c463 100644 --- a/Tests/Memory/MonoLinearArena.spec.cpp +++ b/Tests/Memory/MonoLinearArena.spec.cpp @@ -16,7 +16,7 @@ go_bandit([]() { MonoLinearArena arena{1024}; AssertThat(arena.GetAvailableMemory(), Is().EqualTo(1024)); - AssertThat(arena.GetUsedMemory(), Is().EqualTo(0)); + AssertThat(arena.GetStats()->used, Is().EqualTo(0)); }); it("Can allocate outside the block", [&]() { @@ -50,22 +50,27 @@ go_bandit([]() { MonoLinearArena arena{1024}; void* p = arena.Alloc(sizeof(float)); AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetUsedMemory(), Is().EqualTo(4)); + AssertThat(arena.GetStats()->used, Is().EqualTo(4)); AssertThat(arena.GetAvailableMemory(), Is().EqualTo(1024)); + arena.Free(p, sizeof(float)); }); it("Can allocate with alignment", [&]() { MonoLinearArena arena{1024}; - arena.Alloc(sizeof(bool)); + void* p0 = arena.Alloc(sizeof(bool)); // When padding is not 0 (last ptr is not aligned) - void* p = arena.Alloc(sizeof(float), 8); - AssertThat(p::GetAlignmentPadding(p, 8), Is().EqualTo(0)); + void* p1 = arena.Alloc(sizeof(float), 8); + AssertThat(p::GetAlignmentPadding(p1, 8), Is().EqualTo(0)); // When padding is 0 (last ptr is aligned) void* p2 = arena.Alloc(sizeof(float), 16); AssertThat(p::GetAlignmentPadding(p2, 16), Is().EqualTo(0)); + + arena.Free(p0, sizeof(bool)); + arena.Free(p1, sizeof(float)); + arena.Free(p2, sizeof(float)); }); it("Can allocate after release", [&]() { @@ -73,17 +78,19 @@ go_bandit([]() { arena.Release(); void* p = arena.Alloc(sizeof(float)); AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetUsedMemory(), Is().EqualTo(4)); + AssertThat(arena.GetStats()->used, Is().EqualTo(4)); // Buffer size will be as small as the type (4 bytes) AssertThat(arena.GetAvailableMemory(), Is().EqualTo(1024)); + + arena.Free(p, sizeof(float)); }); it("Can free block after Free", [&]() { MonoLinearArena arena{1024}; void* p = arena.Alloc(256); - AssertThat(arena.GetUsedMemory(), Is().EqualTo(256)); + AssertThat(arena.GetStats()->used, Is().EqualTo(256)); arena.Free(p, 256); - AssertThat(arena.GetUsedMemory(), Is().EqualTo(0)); + AssertThat(arena.GetStats()->used, Is().EqualTo(0)); }); it("Allocates at correct addresses", [&]() { @@ -92,10 +99,13 @@ go_bandit([]() { TArray blocks; arena.GetBlocks(blocks); - void* p = arena.Alloc(sizeof(float)); - AssertThat(p, Is().EqualTo(blocks[0].data)); + void* p1 = arena.Alloc(sizeof(float)); + AssertThat(p1, Is().EqualTo(blocks[0].data)); void* p2 = arena.Alloc(sizeof(float), alignof(float)); AssertThat(p2, Is().EqualTo((u8*)blocks[0].data + 4)); + + arena.Free(p1, sizeof(float)); + arena.Free(p2, sizeof(float)); }); // Move test to Multi linear @@ -104,7 +114,7 @@ go_bandit([]() { void* p = arena.Alloc(sizeof(float*)); // 8 bytes arena.Alloc(sizeof(float)); // 4 bytes - AssertThat(arena.GetUsedMemory(), Is().EqualTo(12)); + AssertThat(arena.GetStats()->used, Is().EqualTo(12)); AssertThat(arena.GetAvailableMemory(), Is().EqualTo(16)); void* p3 = arena.Alloc(sizeof(float*)); // 8 bytes @@ -115,7 +125,7 @@ go_bandit([]() { AssertThat(p, Is().EqualTo(blocks[0].data)); AssertThat(p3, Is().EqualTo(blocks[1].data)); - AssertThat(arena.GetUsedMemory(), Is().EqualTo(8)); + AssertThat(arena.GetStats()->used, Is().EqualTo(8)); AssertThat(arena.GetAvailableMemory(), Is().EqualTo(16)); });*/ }); From a383a9f35046ddb516a62da958bf4e7b8b8992c0 Mon Sep 17 00:00:00 2001 From: muit Date: Sat, 8 Jun 2024 23:10:38 +0100 Subject: [PATCH 04/14] Dont forward format arguments --- Include/Pipe/Core/String.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/Pipe/Core/String.h b/Include/Pipe/Core/String.h index 97ff7026..88e0f135 100644 --- a/Include/Pipe/Core/String.h +++ b/Include/Pipe/Core/String.h @@ -38,7 +38,7 @@ namespace p { String str; std::vformat_to( - std::back_inserter(str), format, std::make_format_args(p::Forward(args)...)); + std::back_inserter(str), format, std::make_format_args(args...)); return Move(str); } @@ -46,7 +46,7 @@ namespace p inline void FormatTo(StringType& buffer, StringView format, Args... args) { std::vformat_to(std::back_inserter(buffer), format, - std::make_format_args(p::Forward(args)...)); + std::make_format_args(args...)); } // Format an string using a compile time format From 76480b85479b3e51c9c7fcbf318b4d32acce04fd Mon Sep 17 00:00:00 2001 From: muit Date: Sat, 29 Jun 2024 23:22:41 +0200 Subject: [PATCH 05/14] Added inherited property reflection & custom debug inspection wip --- Include/Misc/PipeDebug.h | 173 ++++++++++++++++++++-- Include/PipeReflect.h | 5 +- Src/PipeReflect.cpp | 31 +++- Tests/Reflection/MacroReflection.spec.cpp | 4 +- 4 files changed, 187 insertions(+), 26 deletions(-) diff --git a/Include/Misc/PipeDebug.h b/Include/Misc/PipeDebug.h index 71948586..be9e73a7 100644 --- a/Include/Misc/PipeDebug.h +++ b/Include/Misc/PipeDebug.h @@ -15,6 +15,7 @@ static_assert(false, "Imgui v" IMGUI_VERSION " found but PipeDebug requires v1.9 #include "Misc/PipeImGui.h" +#include "Pipe/Core/Map.h" #include "Pipe/Core/Set.h" #include "PipeArrays.h" #include "PipeECS.h" @@ -26,6 +27,11 @@ namespace p // Definition #pragma region Inspection + struct DebugInspectionContext + { + using Callback = TFunction; + TMap registeredTypes; + }; struct DebugECSInspector { protected: @@ -53,8 +59,30 @@ namespace p } }; - bool BeginInspector(const char* name, v2 size = v2{0.f, 0.f}); - void EndInspector(); + bool BeginInspection(const char* name, v2 size = v2{0.f, 0.f}); + void EndInspection(); + + void RegisterTypeInspection(TypeId typeId, const DebugInspectionContext::Callback& callback); + template + void RegisterTypeInspection(Callback callback) + { + RegisterTypeInspection(GetTypeId(), + [callback](StringView label, void* data, TypeId typeId) { + callback(label, *static_cast(data)); + }); + } + void RegisterPipeTypeInspections(); + void RemoveTypeInspection(TypeId typeId); + + void Inspect(StringView label, void* data, TypeId typeId); + template + void Inspect(StringView label, T* data) + { + Inspect(label, data, GetTypeId()); + } + void InspectSetKeyColumn(); + void InspectSetKeyAsText(StringView label); + void InspectSetValueColumn(); #pragma endregion Inspection @@ -106,11 +134,14 @@ namespace p struct DebugContext { + DebugInspectionContext inspection; DebugECSContext ecs; DebugReflectContext reflect; EntityContext* ctx = nullptr; + bool initialized = false; + DebugContext() = default; DebugContext(EntityContext& ctx) : ctx{&ctx} {} @@ -124,10 +155,8 @@ namespace p // Implementation #ifdef P_DEBUG_IMPLEMENTATION - #define EnsureInsideDebug \ - P_EnsureMsg(currentContext, \ - "No ECS Debug context available! Forgot to call " \ - "BeginDebug()?") + #define EnsureInsideDebug \ + P_EnsureMsg(currentContext, "No Debug context available! Forgot to call BeginDebug()?") static DebugContext* currentContext = nullptr; @@ -139,6 +168,112 @@ namespace p #pragma region Inspection i32 DebugECSInspector::uniqueIdCounter = 0; + + + bool BeginInspection(const char* name, v2 size) + { + return false; + } + void EndInspection() {} + + void RegisterTypeInspection(TypeId typeId, const DebugInspectionContext::Callback& callback) + { + if (EnsureInsideDebug) + { + currentContext->inspection.registeredTypes.Insert(typeId, callback); + } + } + void RegisterPipeTypeInspections() + { + RegisterTypeInspection([](StringView label, bool& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::Checkbox("##value", &data); + }); + RegisterTypeInspection([](StringView label, String& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputText("##value", data); + }); + RegisterTypeInspection([](StringView label, StringView& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputText("##value", const_cast(data.data()), data.size(), + ImGuiInputTextFlags_ReadOnly); + }); + RegisterTypeInspection([](StringView label, i8& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_S8, &data); + }); + RegisterTypeInspection([](StringView label, u8& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_U8, &data); + }); + RegisterTypeInspection([](StringView label, i32& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_S32, &data); + }); + RegisterTypeInspection([](StringView label, u32& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_U32, &data); + }); + RegisterTypeInspection([](StringView label, i64& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_S64, &data); + }); + RegisterTypeInspection([](StringView label, u64& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_U64, &data); + }); + RegisterTypeInspection([](StringView label, float& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_Float, &data); + }); + RegisterTypeInspection([](StringView label, double& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_Double, &data); + }); + } + void RemoveTypeInspection(TypeId typeId) + { + if (EnsureInsideDebug) + { + currentContext->inspection.registeredTypes.Remove(typeId); + } + } + void Inspect(StringView label, void* data, TypeId typeId) + { + if (!EnsureInsideDebug) + { + return; + } + + if (auto* cb = currentContext->inspection.registeredTypes.Find(typeId)) + { + (*cb)(label, data, typeId); + } + } + void InspectSetKeyColumn() + { + ImGui::TableSetColumnIndex(0); + } + void InspectSetKeyAsText(StringView label) + { + InspectSetKeyColumn(); + ImGui::TextUnformatted(label.data(), label.data() + label.size()); + } + void InspectSetValueColumn() + { + ImGui::TableSetColumnIndex(1); + } #pragma endregion Inspection @@ -426,6 +561,7 @@ namespace p if (valid) { + BeginInspection("EntityInspector"); String componentLabel; for (const auto& poolInstance : GetDebugCtx().GetPools()) { @@ -452,16 +588,15 @@ namespace p } if (ImGui::CollapsingHeader(componentLabel.c_str(), headerFlags)) { - // UI::Indent(); - // auto* dataType = Cast(type); - // if (data && dataType && UI::BeginInspector("EntityInspector")) - //{ - // UI::InspectChildrenProperties({data, dataType}); - // UI::EndInspector(); - // } - // UI::Unindent(); + ImGui::Indent(); + if (data) + { + Inspect(componentLabel.c_str(), data, poolInstance.componentId); + } + ImGui::Unindent(); } } + EndInspection(); } else { @@ -688,7 +823,7 @@ namespace p return; } - const auto& typeProperties = GetTypeProperties(type); + const auto& typeProperties = GetOwnTypeProperties(type); static String idText; idText.clear(); @@ -845,12 +980,18 @@ namespace p } currentContext = &context; + + if (!currentContext->initialized) + { + RegisterPipeTypeInspections(); + currentContext->initialized = true; + } return true; } void EndDebug() { P_CheckMsg(currentContext, - "Called EndECSDebug() but there was no current ECS Debug Context! Forgot " + "Called EndDebug() but there was no current ECS Debug Context! Forgot " "to call " "BeginDebug()?"); currentContext = nullptr; diff --git a/Include/PipeReflect.h b/Include/PipeReflect.h index 8c88a8f9..3ca987cf 100644 --- a/Include/PipeReflect.h +++ b/Include/PipeReflect.h @@ -337,7 +337,10 @@ namespace p PIPE_API TypeFlags GetTypeFlags(TypeId id); PIPE_API bool HasTypeFlags(TypeId id, TypeFlags flags); PIPE_API bool HasAnyTypeFlags(TypeId id, TypeFlags flags); - PIPE_API TView GetTypeProperties(TypeId id); + /// @return the properties of this type, excluding those of the parent (if any) + PIPE_API TView GetOwnTypeProperties(TypeId id); + /// @return the properties of this type + PIPE_API TView GetTypeProperties(TypeId id); PIPE_API const TypeOps* GetTypeOps(TypeId id); PIPE_API const ObjectTypeOps* GetTypeObjectOps(TypeId id); PIPE_API const ContainerTypeOps* GetTypeContainerOps(TypeId id); diff --git a/Src/PipeReflect.cpp b/Src/PipeReflect.cpp index 80a50b30..efc58c22 100644 --- a/Src/PipeReflect.cpp +++ b/Src/PipeReflect.cpp @@ -35,7 +35,8 @@ namespace p TArray sizes{arena}; TArray names{arena}; TArray flags{arena}; - TArray> properties{arena}; + TArray> ownProperties{arena}; + TArray> allProperties{arena}; TArray operations{arena}; bool initialized = false; @@ -234,11 +235,17 @@ namespace p return details::HasAnyTypeFlags(registry, details::GetTypeIndex(registry, id), flags); } - TView GetTypeProperties(TypeId id) + TView GetOwnTypeProperties(TypeId id) { auto& registry = GetRegistry(); const i32 index = details::GetTypeIndex(registry, id); - return index != NO_INDEX ? registry.properties[index] : TView{}; + return index != NO_INDEX ? registry.ownProperties[index] : TView{}; + } + TView GetTypeProperties(TypeId id) + { + auto& registry = GetRegistry(); + const i32 index = details::GetTypeIndex(registry, id); + return index != NO_INDEX ? registry.allProperties[index] : TView{}; } const TypeOps* GetTypeOps(TypeId id) @@ -288,7 +295,8 @@ namespace p registry.sizes.Insert(index); registry.names.Insert(index); registry.flags.Insert(index); - registry.properties.Insert(index); + registry.ownProperties.Insert(index); + registry.allProperties.Insert(index); registry.operations.Insert(index); return true; } @@ -311,7 +319,15 @@ namespace p void SetTypeParent(TypeId parentId) { P_CheckEditingType; - GetRegistry().parentIds[currentEdit.index] = parentId; + auto& reg = GetRegistry(); + reg.parentIds[currentEdit.index] = parentId; + + // Assign properties from parent + const i32 parentIdx = details::GetTypeIndex(reg, parentId); + if (parentIdx != NO_INDEX) + { + reg.allProperties[currentEdit.index].Append(reg.allProperties[parentIdx]); + } } void SetTypeSize(sizet size) @@ -347,8 +363,9 @@ namespace p void AddTypeProperty(const TypeProperty& property) { P_CheckEditingType; - auto& properties = GetRegistry().properties[currentEdit.index]; - properties.Add(property); + auto& ownProperties = GetRegistry().ownProperties[currentEdit.index]; + const auto& prop = ownProperties.AddRef(property); + GetRegistry().allProperties[currentEdit.index].Add(&prop); } void SetTypeOps(const TypeOps* operations) diff --git a/Tests/Reflection/MacroReflection.spec.cpp b/Tests/Reflection/MacroReflection.spec.cpp index 7a8dcab9..5b2a102b 100644 --- a/Tests/Reflection/MacroReflection.spec.cpp +++ b/Tests/Reflection/MacroReflection.spec.cpp @@ -31,8 +31,8 @@ go_bandit([]() { AssertThat(properties.Size(), Equals(2)); // AssertThat(properties[0].typeId, Equals(p::GetTypeId>())); - AssertThat(properties[0].name.Data(), Equals("value0")); + AssertThat(properties[0]->name.Data(), Equals("value0")); // AssertThat(properties[1].typeId, Equals(p::GetTypeId())); - AssertThat(properties[1].name.Data(), Equals("value1")); + AssertThat(properties[1]->name.Data(), Equals("value1")); }); }); From bce1f9a70fd2e0f650de7ca5a763bad95effd047 Mon Sep 17 00:00:00 2001 From: muit Date: Sun, 30 Jun 2024 23:53:07 +0200 Subject: [PATCH 06/14] Added working property & array inspection --- Include/Misc/PipeDebug.h | 151 +++++++++++++++++++++++++++---- Include/Misc/PipeImGui.h | 50 +++------- Include/PipeECS.h | 6 ++ Include/PipeReflect.h | 32 ++++--- Src/PipeReflect.cpp | 36 +++++--- Tests/Reflection/Traits.spec.cpp | 5 + 6 files changed, 202 insertions(+), 78 deletions(-) diff --git a/Include/Misc/PipeDebug.h b/Include/Misc/PipeDebug.h index 9355692c..750b7ac2 100644 --- a/Include/Misc/PipeDebug.h +++ b/Include/Misc/PipeDebug.h @@ -15,6 +15,7 @@ static_assert(false, "Imgui v" IMGUI_VERSION " found but PipeDebug requires v1.9 #include "Misc/PipeImGui.h" +#include "Pipe/Core/Checks.h" #include "Pipe/Core/Map.h" #include "Pipe/Core/Set.h" #include "PipeArrays.h" @@ -59,7 +60,7 @@ namespace p } }; - bool BeginInspection(const char* name, v2 size = v2{0.f, 0.f}); + bool BeginInspection(const char* label, v2 size = v2{0.f, 0.f}); void EndInspection(); void RegisterTypeInspection(TypeId typeId, const DebugInspectionContext::Callback& callback); @@ -83,6 +84,8 @@ namespace p void InspectSetKeyColumn(); void InspectSetKeyAsText(StringView label); void InspectSetValueColumn(); + bool InspectBeginCategory(p::StringView name, bool isLeaf); + void InspectEndCategory(); #pragma endregion Inspection @@ -159,7 +162,6 @@ namespace p #define EnsureInsideDebug \ P_EnsureMsg(currentContext, "No Debug context available! Forgot to call BeginDebug()?") - static DebugContext* currentContext = nullptr; constexpr LinearColor errorTextColor = LinearColor::FromHex(0xC13E3A); constexpr LinearColor includeColor = LinearColor::FromHex(0x40A832); @@ -167,15 +169,33 @@ namespace p constexpr LinearColor previewColor = LinearColor::FromHex(0x3265A8); + // For internal use only + EntityContext& GetDebugCtx() + { + return *currentContext->ctx; + } + + #pragma region Inspection i32 DebugECSInspector::uniqueIdCounter = 0; - bool BeginInspection(const char* name, v2 size) + bool BeginInspection(const char* label, v2 size) { + const ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp + | ImGuiTableFlags_PadOuterX; + if (ImGui::BeginTable(label, 2, flags, size)) + { + ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_WidthStretch, 0.5f); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 1.f); + return true; + } return false; } - void EndInspection() {} + void EndInspection() + { + ImGui::EndTable(); + } void RegisterTypeInspection(TypeId typeId, const DebugInspectionContext::Callback& callback) { @@ -202,6 +222,16 @@ namespace p ImGui::InputText("##value", const_cast(data.data()), data.size(), ImGuiInputTextFlags_ReadOnly); }); + RegisterTypeInspection([](StringView label, Tag& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + static String str; + str = data.AsString(); + if (ImGui::InputText("##value", str)) + { + data = Tag{str}; + } + }); RegisterTypeInspection([](StringView label, i8& data) { InspectSetKeyAsText(label); InspectSetValueColumn(); @@ -242,6 +272,16 @@ namespace p InspectSetValueColumn(); ImGui::InputScalar("##value", ImGuiDataType_Double, &data); }); + RegisterTypeInspection([](StringView label, Id& data) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + String asString = ToString(data); + ImGui::SetNextItemWidth(100.f); + if (ImGui::InputText("##IdValue", asString, ImGuiInputTextFlags_EscapeClearsAll)) + { + data = IdFromString(asString, &GetDebugCtx()); + } + }); } void RemoveTypeInspection(TypeId typeId) { @@ -252,15 +292,75 @@ namespace p } void Inspect(StringView label, void* data, TypeId typeId) { - if (!EnsureInsideDebug) + if (!EnsureInsideDebug || !data) { return; } - + ImGui::TableNextRow(); + ImGui::PushID(data); if (auto* cb = currentContext->inspection.registeredTypes.Find(typeId)) { (*cb)(label, data, typeId); } + else if (p::HasTypeFlags(typeId, p::TF_Container)) + { + const auto* ops = GetTypeContainerOps(typeId); + P_Check(ops); + i32 size = ops->GetSize(data); + const bool open = InspectBeginCategory(label, size <= 0); + + InspectSetValueColumn(); + String tmpLabel; + Strings::FormatTo(tmpLabel, "{} items", size); + ImGui::Text(tmpLabel); + { // Buttons + // Ignore indent on buttons + const float widthAvailable = + ImGui::GetContentRegionAvail().x + ImGui::GetCurrentWindow()->DC.Indent.x; + ImGui::SameLine(widthAvailable - 50.f); + ImGui::PushStyleCompact(); + if (ImGui::Button("+##AddItem", p::v2(20.f, 18.f))) + { + ops->AddItem(data, nullptr); + } + ImGui::HelpTooltip("Add: Add one item"); + ImGui::SameLine(); + if (ImGui::Button("Clr##Empty", p::v2(20.f, 18.f))) + { + ops->Clear(data); + } + ImGui::HelpTooltip("Clear: Remove all items of the array"); + ImGui::PopStyleCompact(); + } + + if (open) + { + size = ops->GetSize(data); // Update size + for (i32 i = 0; i < size; ++i) + { + tmpLabel.clear(); + Strings::FormatTo(tmpLabel, "Index {}", i); + Inspect(tmpLabel, ops->GetItem(data, i), ops->itemType); + } + InspectEndCategory(); + } + } + else if (p::HasTypeFlags(typeId, p::TF_Struct)) + { + auto props = GetTypeProperties(typeId); + if (InspectBeginCategory(label, props.Size() <= 0)) + { + for (const auto* prop : props) + { + ImGui::BeginDisabled(!prop->HasFlag(PF_Edit)); + Inspect(prop->name.AsString(), prop->access(data), prop->typeId); + ImGui::EndDisabled(); + } + InspectEndCategory(); + } + } + + ImGui::PopID(); } void InspectSetKeyColumn() { @@ -269,22 +369,33 @@ namespace p void InspectSetKeyAsText(StringView label) { InspectSetKeyColumn(); + ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted(label.data(), label.data() + label.size()); } void InspectSetValueColumn() { ImGui::TableSetColumnIndex(1); } + bool InspectBeginCategory(p::StringView name, bool isLeaf) + { + InspectSetKeyColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::Unindent(); + const ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_AllowItemOverlap + | ImGuiTreeNodeFlags_SpanAllColumns + | (isLeaf ? ImGuiTreeNodeFlags_Leaf : 0); + bool bOpen = ImGui::TreeNodeEx(name.data(), flags); + ImGui::Indent(); + return bOpen; + } + void InspectEndCategory() + { + ImGui::TreePop(); + } #pragma endregion Inspection #pragma region ECS - // For internal use only - EntityContext& GetDebugCtx() - { - return *currentContext->ctx; - } - using DrawNodeAccess = TAccessRef; namespace details { @@ -560,9 +671,8 @@ namespace p ImGui::EndMenuBar(); } - if (valid) + if (valid && BeginInspection("##Inspector")) { - BeginInspection("EntityInspector"); String componentLabel; for (const auto& poolInstance : GetDebugCtx().GetPools()) { @@ -581,18 +691,27 @@ namespace p continue; } - ImGuiTreeNodeFlags headerFlags = ImGuiTreeNodeFlags_DefaultOpen; + ImGuiTreeNodeFlags headerFlags = + ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAllColumns; void* data = poolInstance.GetPool()->TryGetVoid(inspector.id); if (!data) { headerFlags |= ImGuiTreeNodeFlags_Leaf; } + ImGui::TableNextRow(); + InspectSetKeyColumn(); if (ImGui::CollapsingHeader(componentLabel.c_str(), headerFlags)) { ImGui::Indent(); if (data) { - Inspect(componentLabel.c_str(), data, poolInstance.componentId); + auto props = GetTypeProperties(poolInstance.componentId); + for (const auto* prop : props) + { + ImGui::BeginDisabled(!prop->HasFlag(PF_Edit)); + Inspect(prop->name.AsString(), prop->access(data), prop->typeId); + ImGui::EndDisabled(); + } } ImGui::Unindent(); } diff --git a/Include/Misc/PipeImGui.h b/Include/Misc/PipeImGui.h index 64c3b84c..d2600931 100644 --- a/Include/Misc/PipeImGui.h +++ b/Include/Misc/PipeImGui.h @@ -160,48 +160,26 @@ namespace ImGui desc_id, ImVec4{col.r, col.g, col.b, col.a}, flags, ImVec2{size.x, size.y}); } - inline void HelpTooltip(p::StringView text, float delay = 1.f) + inline void HelpTooltip(p::StringView text, + ImGuiHoveredFlags flags = ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_NoSharedDelay) { - static ImGuiID currentHelpItemId = 0; - - ImGuiID itemId = GetCurrentContext()->LastItemData.ID; - if (IsItemHovered()) - { - bool show = true; - if (delay > 0.f) - { - static p::DateTime hoverStartTime; - const p::DateTime now = p::DateTime::Now(); - if (itemId != currentHelpItemId) - { - // Reset help tooltip countdown - currentHelpItemId = itemId; - hoverStartTime = now; - } - show = (now - hoverStartTime).GetTotalSeconds() > delay; - } - - if (show) - { - PushStyleVar(ImGuiStyleVar_WindowPadding, p::v2{4.f, 3.f}); - BeginTooltip(); - PushTextWrapPos(GetFontSize() * 35.0f); - AlignTextToFramePadding(); - TextUnformatted(text.data()); - PopTextWrapPos(); - EndTooltip(); - PopStyleVar(); - } - } - else if (itemId == currentHelpItemId) + if (IsItemHovered(flags)) { - currentHelpItemId = 0; + PushStyleVar(ImGuiStyleVar_WindowPadding, p::v2{4.f, 3.f}); + BeginTooltip(); + PushTextWrapPos(GetFontSize() * 35.0f); + AlignTextToFramePadding(); + TextUnformatted(text.data()); + PopTextWrapPos(); + EndTooltip(); + PopStyleVar(); } } - inline void HelpMarker(p::StringView text) + inline void HelpMarker(p::StringView text, + ImGuiHoveredFlags flags = ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_NoSharedDelay) { TextDisabled("(?)"); - HelpTooltip(text, 0.f); + HelpTooltip(text, flags); } inline bool DrawFilterWithHint(ImGuiTextFilter& filter, const char* label = "Filter (inc,-exc)", diff --git a/Include/PipeECS.h b/Include/PipeECS.h index 1dbc9942..e418e62c 100644 --- a/Include/PipeECS.h +++ b/Include/PipeECS.h @@ -1915,6 +1915,9 @@ namespace p struct PIPE_API CParent { + P_STRUCT(CParent) + + P_PROP(children, PF_Edit) TArray children; }; PIPE_API void Read(Reader& ct, CParent& val); @@ -1922,6 +1925,9 @@ namespace p struct PIPE_API CChild { + P_STRUCT(CChild) + + P_PROP(parent, PF_Edit) Id parent = NoId; }; PIPE_API void Read(Reader& ct, CChild& val); diff --git a/Include/PipeReflect.h b/Include/PipeReflect.h index 3ca987cf..c45fd210 100644 --- a/Include/PipeReflect.h +++ b/Include/PipeReflect.h @@ -47,7 +47,7 @@ namespace p template consteval bool HasExternalBuildType() { - return requires(const T v) { BuildType(&v); }; + return IsArray() || requires(const T v) { BuildType(&v); }; } template @@ -301,6 +301,7 @@ namespace p using RemoveItemFunc = void(void*, i32); using ClearFunc = void(void*); + TypeId itemType = TypeId::None(); GetDataFunc* getData = nullptr; GetSizeFunc* getSize = nullptr; GetItemFunc* getItem = nullptr; @@ -379,11 +380,11 @@ namespace p */ // Resolve the right BuildType function to call - template + /*template void BuildType(const T*) requires(HasMemberBuildType()) { - T::BuildType(); - } + T::BuildType(); + }*/ template @@ -758,42 +759,43 @@ P_NATIVE_NAMED(p::Color, "Color") // Build array types template -inline void BuildType(const p::TArray*) +inline void BuildType(const T*) requires(p::IsArray()) { // clang-format off static p::ContainerTypeOps ops{ + .itemType = p::RegisterTypeId(), .getData = [](void* data) { - return (void*)static_cast*>(data)->Data(); + return (void*)static_cast(data)->Data(); }, .getSize = [](void* data) { - return static_cast*>(data)->Size(); + return static_cast(data)->Size(); }, .getItem = [](void* data, p::i32 index) { - return (void*)(static_cast*>(data)->Data() + index); + return (void*)(static_cast(data)->Data() + index); }, .addItem = [](void* data, void* item) { if (item) { - auto& itemRef = *static_cast(item); - if constexpr (p::IsCopyAssignable) + auto& itemRef = *static_cast(item); + if constexpr (p::IsCopyAssignable) { - static_cast*>(data)->Add(itemRef); + static_cast(data)->Add(itemRef); } else { - static_cast*>(data)->Add(Move(itemRef)); + static_cast(data)->Add(p::Move(itemRef)); } } else { - static_cast*>(data)->Add(); + static_cast(data)->Add(); } }, .removeItem = [](void* data, p::i32 index) { - static_cast*>(data)->RemoveAt(index); + static_cast(data)->RemoveAt(index); }, .clear = [](void* data) { - static_cast*>(data)->Clear(); + static_cast(data)->Clear(); } }; // clang-format on diff --git a/Src/PipeReflect.cpp b/Src/PipeReflect.cpp index e086f68d..b876d77b 100644 --- a/Src/PipeReflect.cpp +++ b/Src/PipeReflect.cpp @@ -268,7 +268,7 @@ namespace p { auto& registry = GetRegistry(); const i32 index = details::GetTypeIndex(registry, id); - return (index != NO_INDEX && (registry.flags[index] & TF_Object) == TF_Object) + return (index != NO_INDEX && (registry.flags[index] & TF_Container) == TF_Container) ? static_cast(registry.operations[index]) : nullptr; } @@ -309,11 +309,33 @@ namespace p { return; } + + auto& reg = GetRegistry(); + + { // Cache inherited properties + auto& allProperties = reg.allProperties[currentEdit.index]; + allProperties.Clear(false); + + // Assign properties from parent + const i32 parentIdx = details::GetTypeIndex(reg, reg.parentIds[currentEdit.index]); + if (parentIdx != NO_INDEX) + { + allProperties.Append(reg.allProperties[parentIdx]); + } + // Assign own properties + const auto& ownProperties = reg.ownProperties[currentEdit.index]; + allProperties.ReserveMore(ownProperties.Size()); + for (auto& ownProp : ownProperties) + { + allProperties.Add(&ownProp); + } + } + currentEdit.typeStack.RemoveLast(); // Apply the index (that could have changed) currentEdit.index = currentEdit.typeStack.IsEmpty() ? NO_INDEX - : details::GetTypeIndex(GetRegistry(), currentEdit.typeStack.Last()); + : details::GetTypeIndex(reg, currentEdit.typeStack.Last()); } void SetTypeParent(TypeId parentId) @@ -321,13 +343,6 @@ namespace p P_CheckEditingType; auto& reg = GetRegistry(); reg.parentIds[currentEdit.index] = parentId; - - // Assign properties from parent - const i32 parentIdx = details::GetTypeIndex(reg, parentId); - if (parentIdx != NO_INDEX) - { - reg.allProperties[currentEdit.index].Append(reg.allProperties[parentIdx]); - } } void SetTypeSize(sizet size) @@ -364,8 +379,7 @@ namespace p { P_CheckEditingType; auto& ownProperties = GetRegistry().ownProperties[currentEdit.index]; - const auto& prop = ownProperties.AddRef(property); - GetRegistry().allProperties[currentEdit.index].Add(&prop); + ownProperties.Add(property); } void SetTypeOps(const TypeOps* operations) diff --git a/Tests/Reflection/Traits.spec.cpp b/Tests/Reflection/Traits.spec.cpp index dc8babba..9ce97470 100644 --- a/Tests/Reflection/Traits.spec.cpp +++ b/Tests/Reflection/Traits.spec.cpp @@ -82,5 +82,10 @@ go_bandit([]() { AssertThat(p::HasSuper(), Is().False()); AssertThat(p::HasSuper(), Is().True()); }); + + it("Can build type on Arrays", []() { + AssertThat(p::CanBuildType>(), Is().True()); + AssertThat(p::HasExternalBuildType>(), Is().True()); + }); }); }); From 1eaf0fc99745d3c320dfe6f674a59a9980b37aee Mon Sep 17 00:00:00 2001 From: muit Date: Mon, 1 Jul 2024 22:27:41 +0200 Subject: [PATCH 07/14] Added contextual menu buttons --- Include/Misc/PipeDebug.h | 179 ++++++++++++++++++++++++++++----------- Include/Misc/PipeImGui.h | 4 +- 2 files changed, 130 insertions(+), 53 deletions(-) diff --git a/Include/Misc/PipeDebug.h b/Include/Misc/PipeDebug.h index 750b7ac2..568bac9a 100644 --- a/Include/Misc/PipeDebug.h +++ b/Include/Misc/PipeDebug.h @@ -28,10 +28,30 @@ namespace p // Definition #pragma region Inspection + struct TypeInspection + { + using RowCallback = + std::function; + using ChildrenCallback = std::function; + template + using TRowCallback = std::function; + template + using TChildrenCallback = std::function; + + RowCallback onDrawRow; + ChildrenCallback onDrawChildren; + }; + struct DebugInspectionContext { - using Callback = TFunction; - TMap registeredTypes; + struct PropStack + { + TypeId typeId; + void* data = nullptr; + i32 index = NO_INDEX; + }; + TMap registeredTypes; + TArray propStack; }; struct DebugECSInspector { @@ -63,14 +83,23 @@ namespace p bool BeginInspection(const char* label, v2 size = v2{0.f, 0.f}); void EndInspection(); - void RegisterTypeInspection(TypeId typeId, const DebugInspectionContext::Callback& callback); - template - void RegisterTypeInspection(Callback callback) + void RegisterTypeInspection(TypeId typeId, TypeInspection::RowCallback onDrawRow, + TypeInspection::ChildrenCallback onDrawChildren = {}); + template + void RegisterTypeInspection(TypeInspection::TRowCallback onDrawRow, + TypeInspection::TChildrenCallback onDrawChildren = {}) { - RegisterTypeInspection(GetTypeId(), - [callback](StringView label, void* data, TypeId typeId) { - callback(label, *static_cast(data)); - }); + // clang-format off + TypeInspection::RowCallback onDrawRowBase = + onDrawRow? [onDrawRow = Move(onDrawRow)](StringView label, void* data, TypeId typeId, bool& open) { + onDrawRow(label, *static_cast(data), open); + } : TypeInspection::RowCallback{}; + TypeInspection::ChildrenCallback onDrawChildrenBase = + onDrawChildren? [onDrawChildren = Move(onDrawChildren)](void* data, TypeId typeId) { + onDrawChildren(*static_cast(data)); + } : TypeInspection::ChildrenCallback{}; + // clang-format on + RegisterTypeInspection(GetTypeId(), Move(onDrawRowBase), Move(onDrawChildrenBase)); } void RegisterPipeTypeInspections(); void RemoveTypeInspection(TypeId typeId); @@ -197,32 +226,34 @@ namespace p ImGui::EndTable(); } - void RegisterTypeInspection(TypeId typeId, const DebugInspectionContext::Callback& callback) + void RegisterTypeInspection(TypeId typeId, TypeInspection::RowCallback onDrawRow, + TypeInspection::ChildrenCallback onDrawChildren) { if (EnsureInsideDebug) { - currentContext->inspection.registeredTypes.Insert(typeId, callback); + currentContext->inspection.registeredTypes.Insert( + typeId, {Move(onDrawRow), Move(onDrawChildren)}); } } void RegisterPipeTypeInspections() { - RegisterTypeInspection([](StringView label, bool& data) { + RegisterTypeInspection([](StringView label, bool& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); ImGui::Checkbox("##value", &data); }); - RegisterTypeInspection([](StringView label, String& data) { + RegisterTypeInspection([](StringView label, String& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); ImGui::InputText("##value", data); }); - RegisterTypeInspection([](StringView label, StringView& data) { + RegisterTypeInspection([](StringView label, StringView& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); ImGui::InputText("##value", const_cast(data.data()), data.size(), ImGuiInputTextFlags_ReadOnly); }); - RegisterTypeInspection([](StringView label, Tag& data) { + RegisterTypeInspection([](StringView label, Tag& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); static String str; @@ -232,47 +263,47 @@ namespace p data = Tag{str}; } }); - RegisterTypeInspection([](StringView label, i8& data) { + RegisterTypeInspection([](StringView label, i8& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); ImGui::InputScalar("##value", ImGuiDataType_S8, &data); }); - RegisterTypeInspection([](StringView label, u8& data) { + RegisterTypeInspection([](StringView label, u8& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); ImGui::InputScalar("##value", ImGuiDataType_U8, &data); }); - RegisterTypeInspection([](StringView label, i32& data) { + RegisterTypeInspection([](StringView label, i32& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); ImGui::InputScalar("##value", ImGuiDataType_S32, &data); }); - RegisterTypeInspection([](StringView label, u32& data) { + RegisterTypeInspection([](StringView label, u32& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); ImGui::InputScalar("##value", ImGuiDataType_U32, &data); }); - RegisterTypeInspection([](StringView label, i64& data) { + RegisterTypeInspection([](StringView label, i64& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); ImGui::InputScalar("##value", ImGuiDataType_S64, &data); }); - RegisterTypeInspection([](StringView label, u64& data) { + RegisterTypeInspection([](StringView label, u64& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); ImGui::InputScalar("##value", ImGuiDataType_U64, &data); }); - RegisterTypeInspection([](StringView label, float& data) { + RegisterTypeInspection([](StringView label, float& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); ImGui::InputScalar("##value", ImGuiDataType_Float, &data); }); - RegisterTypeInspection([](StringView label, double& data) { + RegisterTypeInspection([](StringView label, double& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); ImGui::InputScalar("##value", ImGuiDataType_Double, &data); }); - RegisterTypeInspection([](StringView label, Id& data) { + RegisterTypeInspection([](StringView label, Id& data, bool& open) { InspectSetKeyAsText(label); InspectSetValueColumn(); String asString = ToString(data); @@ -296,60 +327,107 @@ namespace p { return; } + + auto& ins = currentContext->inspection; + ImGui::TableNextRow(); ImGui::PushID(data); - if (auto* cb = currentContext->inspection.registeredTypes.Find(typeId)) + bool open = false; + auto* it = ins.registeredTypes.Find(typeId); + if (it && it->onDrawRow) { - (*cb)(label, data, typeId); + it->onDrawRow(label, data, typeId, open); } else if (p::HasTypeFlags(typeId, p::TF_Container)) { const auto* ops = GetTypeContainerOps(typeId); P_Check(ops); - i32 size = ops->GetSize(data); - const bool open = InspectBeginCategory(label, size <= 0); + const i32 size = ops->GetSize(data); + open = InspectBeginCategory(label, size <= 0); InspectSetValueColumn(); - String tmpLabel; - Strings::FormatTo(tmpLabel, "{} items", size); - ImGui::Text(tmpLabel); - { // Buttons - // Ignore indent on buttons - const float widthAvailable = - ImGui::GetContentRegionAvail().x + ImGui::GetCurrentWindow()->DC.Indent.x; - ImGui::SameLine(widthAvailable - 50.f); - ImGui::PushStyleCompact(); - if (ImGui::Button("+##AddItem", p::v2(20.f, 18.f))) + ImGui::Text("%i items", size); + + const float widthAvailable = + ImGui::GetContentRegionAvail().x + ImGui::GetCurrentWindow()->DC.Indent.x; + ImGui::SameLine(widthAvailable - 20.f); + if (ImGui::SmallButton("...")) + { + ImGui::OpenPopup("ContextualSettings"); + } + if (ImGui::BeginPopupContextItem("ContextualSettings")) + { + if (ImGui::MenuItem("Add")) { ops->AddItem(data, nullptr); + ImGui::CloseCurrentPopup(); } ImGui::HelpTooltip("Add: Add one item"); - ImGui::SameLine(); - if (ImGui::Button("Clr##Empty", p::v2(20.f, 18.f))) + if (ImGui::MenuItem("Clear")) { ops->Clear(data); + ImGui::CloseCurrentPopup(); } ImGui::HelpTooltip("Clear: Remove all items of the array"); - ImGui::PopStyleCompact(); + ImGui::EndPopup(); } + } + else if (p::HasTypeFlags(typeId, p::TF_Struct)) + { + auto props = GetTypeProperties(typeId); + open = InspectBeginCategory(label, props.Size() <= 0); + } - if (open) + if (!ins.propStack.IsEmpty()) // Container item buttons + { + const auto& prop = ins.propStack[0]; + + const float widthAvailable = + ImGui::GetContentRegionAvail().x + ImGui::GetCurrentWindow()->DC.Indent.x; + ImGui::SameLine(widthAvailable - 20.f); + if (ImGui::SmallButton("...")) + { + ImGui::OpenPopup("ContextualSettings"); + } + if (ImGui::BeginPopupContextItem("ContextualSettings")) { - size = ops->GetSize(data); // Update size + if (p::HasTypeFlags(prop.typeId, p::TF_Container) && ImGui::MenuItem("Remove")) + { + const auto* ops = GetTypeContainerOps(prop.typeId); + ops->RemoveItem(prop.data, prop.index); + ImGui::CloseCurrentPopup(); + } + ImGui::HelpTooltip("Remove: Remove this item from its array"); + ImGui::EndPopup(); + } + } + + if (open) + { + if (it && it->onDrawChildren) + { + it->onDrawChildren(data, typeId); + } + else if (p::HasTypeFlags(typeId, p::TF_Container)) + { + const auto* ops = GetTypeContainerOps(typeId); + String tmpLabel; + const i32 size = ops->GetSize(data); + ins.propStack.Add({typeId, data, 0}); for (i32 i = 0; i < size; ++i) { tmpLabel.clear(); Strings::FormatTo(tmpLabel, "Index {}", i); Inspect(tmpLabel, ops->GetItem(data, i), ops->itemType); + + ++ins.propStack.Last().index; } + ins.propStack.RemoveLast(); InspectEndCategory(); } - } - else if (p::HasTypeFlags(typeId, p::TF_Struct)) - { - auto props = GetTypeProperties(typeId); - if (InspectBeginCategory(label, props.Size() <= 0)) + else if (p::HasTypeFlags(typeId, p::TF_Struct)) { + auto props = GetTypeProperties(typeId); for (const auto* prop : props) { ImGui::BeginDisabled(!prop->HasFlag(PF_Edit)); @@ -359,7 +437,6 @@ namespace p InspectEndCategory(); } } - ImGui::PopID(); } void InspectSetKeyColumn() @@ -702,9 +779,9 @@ namespace p InspectSetKeyColumn(); if (ImGui::CollapsingHeader(componentLabel.c_str(), headerFlags)) { - ImGui::Indent(); if (data) { + ImGui::Indent(); auto props = GetTypeProperties(poolInstance.componentId); for (const auto* prop : props) { @@ -712,8 +789,8 @@ namespace p Inspect(prop->name.AsString(), prop->access(data), prop->typeId); ImGui::EndDisabled(); } + ImGui::Unindent(); } - ImGui::Unindent(); } } EndInspection(); diff --git a/Include/Misc/PipeImGui.h b/Include/Misc/PipeImGui.h index d2600931..741b0a03 100644 --- a/Include/Misc/PipeImGui.h +++ b/Include/Misc/PipeImGui.h @@ -161,7 +161,7 @@ namespace ImGui } inline void HelpTooltip(p::StringView text, - ImGuiHoveredFlags flags = ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_NoSharedDelay) + ImGuiHoveredFlags flags = ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_NoSharedDelay) { if (IsItemHovered(flags)) { @@ -176,7 +176,7 @@ namespace ImGui } } inline void HelpMarker(p::StringView text, - ImGuiHoveredFlags flags = ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_NoSharedDelay) + ImGuiHoveredFlags flags = ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_NoSharedDelay) { TextDisabled("(?)"); HelpTooltip(text, flags); From 6918792abc10e495f8493dfd286b3ea04d758643 Mon Sep 17 00:00:00 2001 From: muit Date: Mon, 1 Jul 2024 22:32:05 +0200 Subject: [PATCH 08/14] Fix warning on rand --- Include/PipeMath.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/PipeMath.h b/Include/PipeMath.h index a330c354..94a8a095 100644 --- a/Include/PipeMath.h +++ b/Include/PipeMath.h @@ -66,7 +66,7 @@ namespace p /** Returns a random float between 0 and 1, inclusive. */ inline PIPE_API float Rand01() { - return (float)Rand() / RAND_MAX; + return (float)Rand() / (float)RAND_MAX; } inline PIPE_API float Random(float min, float max) From 2c9247a07efee3ea81860f4c3c1611f59286ec01 Mon Sep 17 00:00:00 2001 From: muit Date: Mon, 1 Jul 2024 22:36:09 +0200 Subject: [PATCH 09/14] Fix compiled missing function --- Include/PipeReflect.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Include/PipeReflect.h b/Include/PipeReflect.h index c45fd210..22bfacfd 100644 --- a/Include/PipeReflect.h +++ b/Include/PipeReflect.h @@ -380,11 +380,11 @@ namespace p */ // Resolve the right BuildType function to call - /*template + template void BuildType(const T*) requires(HasMemberBuildType()) { - T::BuildType(); - }*/ + T::BuildType(); + } template @@ -518,7 +518,7 @@ namespace p { T::BuildType(); } - else + else if constexpr (HasExternalBuildType()) { BuildType((const T*)nullptr); } From 8cf3aba9f5d396643f957ec7cc215628fb6e7e8d Mon Sep 17 00:00:00 2001 From: muit Date: Mon, 1 Jul 2024 22:45:38 +0200 Subject: [PATCH 10/14] Fixed invalid build type functions --- Include/PipeReflect.h | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/Include/PipeReflect.h b/Include/PipeReflect.h index 22bfacfd..a1325113 100644 --- a/Include/PipeReflect.h +++ b/Include/PipeReflect.h @@ -47,7 +47,7 @@ namespace p template consteval bool HasExternalBuildType() { - return IsArray() || requires(const T v) { BuildType(&v); }; + return requires(const T v) { BuildType(&v); }; } template @@ -580,11 +580,14 @@ namespace p static inline TTypeAutoRegister instance; \ }; -#define P_NATIVE(type) \ - inline void BuildType(const type*) \ - { \ - p::AddTypeFlags(p::TF_Native); \ - }; \ +#define P_NATIVE(type) \ + namespace p \ + { \ + inline void BuildType(const type*) \ + { \ + p::AddTypeFlags(p::TF_Native); \ + }; \ + } \ P_AUTOREGISTER_TYPE(type) #define P_NATIVE_NAMED(type, name) \ @@ -758,10 +761,12 @@ P_NATIVE_NAMED(p::HSVColor, "HSVColor") P_NATIVE_NAMED(p::Color, "Color") // Build array types -template -inline void BuildType(const T*) requires(p::IsArray()) +namespace p { - // clang-format off + template + inline void BuildType(const T*) requires(p::IsArray()) + { + // clang-format off static p::ContainerTypeOps ops{ .itemType = p::RegisterTypeId(), .getData = [](void* data) { @@ -798,10 +803,11 @@ inline void BuildType(const T*) requires(p::IsArray()) static_cast(data)->Clear(); } }; - // clang-format on - p::AddTypeFlags(p::TF_Container); - p::SetTypeOps(&ops); -}; + // clang-format on + p::AddTypeFlags(p::TF_Container); + p::SetTypeOps(&ops); + }; +} // namespace p #pragma endregion PipeTypesSupport From dab1875a9f62501f10058148cca4e18d4d1cc9b8 Mon Sep 17 00:00:00 2001 From: muit Date: Tue, 2 Jul 2024 19:27:21 +0200 Subject: [PATCH 11/14] Updated Bandit --- .gitmodules | 5 +---- Extern/Bandit | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index a9c84845..13f9ef42 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "Extern/Bandit"] path = Extern/Bandit - url = https://github.com/banditcpp/bandit + url = https://github.com/banditcpp/bandit.git [submodule "Extern/spdlog"] path = Extern/spdlog url = https://github.com/gabime/spdlog.git @@ -16,9 +16,6 @@ [submodule "Extern/utfcpp"] path = Extern/utfcpp url = https://github.com/nemtrif/utfcpp.git -[submodule "Extern/simdjson"] - path = Extern/simdjson - url = https://github.com/simdjson/simdjson.git [submodule "Extern/yyjson"] path = Extern/yyjson url = https://github.com/ibireme/yyjson.git diff --git a/Extern/Bandit b/Extern/Bandit index 77f50861..a16c7427 160000 --- a/Extern/Bandit +++ b/Extern/Bandit @@ -1 +1 @@ -Subproject commit 77f50861c09d794af9ae5f65111b330d9b721054 +Subproject commit a16c742727f188d28793b57784eac0ae6df5a1fc From 86ec03f5a2dc7cc82c824111ae24fc896071e2b0 Mon Sep 17 00:00:00 2001 From: muit Date: Tue, 2 Jul 2024 19:35:17 +0200 Subject: [PATCH 12/14] Fix clang 16 error on MacOS --- Tests/Core/String.spec.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Tests/Core/String.spec.cpp b/Tests/Core/String.spec.cpp index a7c99a73..ab1ef4e9 100644 --- a/Tests/Core/String.spec.cpp +++ b/Tests/Core/String.spec.cpp @@ -90,18 +90,19 @@ go_bandit([]() { }); it("Convert u8 to u16", [&]() { TString utf8_with_surrogates = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e"; - TString utf16result = Strings::Convert>(utf8_with_surrogates); + TString utf16result = + Strings::Convert>(utf8_with_surrogates); AssertThat(utf16result.size(), Equals(4)); - AssertThat(utf16result[2], Equals(0xd834)); - AssertThat(utf16result[3], Equals(0xdd1e)); + AssertThat(utf16result[2] == 0xd834, Is().True()); + AssertThat(utf16result[3] == 0xdd1e, Is().True()); }); it("Convert u32 to u8", [&]() { TString utf32string = {0x448, 0x65E5, 0x10346}; - TString utf8result = Strings::Convert>(utf32string); + TString utf8result = Strings::Convert>(utf32string); AssertThat(utf8result.size(), Equals(9)); }); it("Convert u8 to u32", [&]() { - TString twochars = "\xe6\x97\xa5\xd1\x88"; + TString twochars = "\xe6\x97\xa5\xd1\x88"; TString utf32result = Strings::Convert>(twochars); AssertThat(utf32result.size(), Equals(2)); }); From 4357293819f093ff76c61cd49a9e91aac24ca1d3 Mon Sep 17 00:00:00 2001 From: muit Date: Tue, 2 Jul 2024 19:44:06 +0200 Subject: [PATCH 13/14] Use Clang 18 for Windows --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76274257..160d2ea4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: - name: windows-clang os: windows-latest os-name: Windows - compiler: clang-16 + compiler: clang-18 - name: linux-clang os: ubuntu-latest os-name: Linux From 19c7866e0b6596e9349b464eae578b583bf6efe1 Mon Sep 17 00:00:00 2001 From: muit Date: Tue, 2 Jul 2024 19:47:17 +0200 Subject: [PATCH 14/14] Clang 17 maybe? --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 160d2ea4..a3cf9733 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: - name: windows-clang os: windows-latest os-name: Windows - compiler: clang-18 + compiler: clang-17 - name: linux-clang os: ubuntu-latest os-name: Linux