diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76274257..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-16 + compiler: clang-17 - name: linux-clang os: ubuntu-latest os-name: Linux 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/.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/Extern/Bandit b/Extern/Bandit index 77f50861..a16c7427 160000 --- a/Extern/Bandit +++ b/Extern/Bandit @@ -1 +1 @@ -Subproject commit 77f50861c09d794af9ae5f65111b330d9b721054 +Subproject commit a16c742727f188d28793b57784eac0ae6df5a1fc diff --git a/Include/Misc/PipeDebug.h b/Include/Misc/PipeDebug.h index aa215739..568bac9a 100644 --- a/Include/Misc/PipeDebug.h +++ b/Include/Misc/PipeDebug.h @@ -15,6 +15,8 @@ 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" #include "PipeECS.h" @@ -26,6 +28,31 @@ 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 + { + struct PropStack + { + TypeId typeId; + void* data = nullptr; + i32 index = NO_INDEX; + }; + TMap registeredTypes; + TArray propStack; + }; struct DebugECSInspector { protected: @@ -53,8 +80,41 @@ namespace p } }; - bool BeginInspector(const char* name, v2 size = v2{0.f, 0.f}); - void EndInspector(); + bool BeginInspection(const char* label, v2 size = v2{0.f, 0.f}); + void EndInspection(); + + void RegisterTypeInspection(TypeId typeId, TypeInspection::RowCallback onDrawRow, + TypeInspection::ChildrenCallback onDrawChildren = {}); + template + void RegisterTypeInspection(TypeInspection::TRowCallback onDrawRow, + TypeInspection::TChildrenCallback onDrawChildren = {}) + { + // 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); + + 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(); + bool InspectBeginCategory(p::StringView name, bool isLeaf); + void InspectEndCategory(); #pragma endregion Inspection @@ -107,10 +167,17 @@ namespace p struct DebugContext { + DebugInspectionContext inspection; DebugECSContext ecs; DebugReflectContext reflect; EntityContext* ctx = nullptr; + + bool initialized = false; + + + DebugContext() = default; + DebugContext(EntityContext& ctx) : ctx{&ctx} {} }; bool BeginDebug(DebugContext& Context); @@ -121,11 +188,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; constexpr LinearColor errorTextColor = LinearColor::FromHex(0xC13E3A); @@ -134,18 +198,281 @@ namespace p constexpr LinearColor previewColor = LinearColor::FromHex(0x3265A8); + // For internal use only + EntityContext& GetDebugCtx() + { + return *currentContext->ctx; + } + + #pragma region Inspection i32 DebugECSInspector::uniqueIdCounter = 0; - #pragma endregion Inspection - #pragma region ECS - // For internal use only - EntityContext& GetDebugCtx() + bool BeginInspection(const char* label, v2 size) { - return *currentContext->ctx; + 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() + { + ImGui::EndTable(); + } + + void RegisterTypeInspection(TypeId typeId, TypeInspection::RowCallback onDrawRow, + TypeInspection::ChildrenCallback onDrawChildren) + { + if (EnsureInsideDebug) + { + currentContext->inspection.registeredTypes.Insert( + typeId, {Move(onDrawRow), Move(onDrawChildren)}); + } + } + void RegisterPipeTypeInspections() + { + RegisterTypeInspection([](StringView label, bool& data, bool& open) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::Checkbox("##value", &data); + }); + RegisterTypeInspection([](StringView label, String& data, bool& open) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputText("##value", 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, bool& open) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + static String str; + str = data.AsString(); + if (ImGui::InputText("##value", str)) + { + data = Tag{str}; + } + }); + RegisterTypeInspection([](StringView label, i8& data, bool& open) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_S8, &data); + }); + RegisterTypeInspection([](StringView label, u8& data, bool& open) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_U8, &data); + }); + RegisterTypeInspection([](StringView label, i32& data, bool& open) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_S32, &data); + }); + RegisterTypeInspection([](StringView label, u32& data, bool& open) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_U32, &data); + }); + RegisterTypeInspection([](StringView label, i64& data, bool& open) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_S64, &data); + }); + RegisterTypeInspection([](StringView label, u64& data, bool& open) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_U64, &data); + }); + RegisterTypeInspection([](StringView label, float& data, bool& open) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_Float, &data); + }); + RegisterTypeInspection([](StringView label, double& data, bool& open) { + InspectSetKeyAsText(label); + InspectSetValueColumn(); + ImGui::InputScalar("##value", ImGuiDataType_Double, &data); + }); + RegisterTypeInspection([](StringView label, Id& data, bool& open) { + 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) + { + if (EnsureInsideDebug) + { + currentContext->inspection.registeredTypes.Remove(typeId); + } + } + void Inspect(StringView label, void* data, TypeId typeId) + { + if (!EnsureInsideDebug || !data) + { + return; + } + + auto& ins = currentContext->inspection; + + ImGui::TableNextRow(); + ImGui::PushID(data); + bool open = false; + auto* it = ins.registeredTypes.Find(typeId); + if (it && it->onDrawRow) + { + it->onDrawRow(label, data, typeId, open); + } + else if (p::HasTypeFlags(typeId, p::TF_Container)) + { + const auto* ops = GetTypeContainerOps(typeId); + P_Check(ops); + const i32 size = ops->GetSize(data); + open = InspectBeginCategory(label, size <= 0); + + InspectSetValueColumn(); + 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"); + if (ImGui::MenuItem("Clear")) + { + ops->Clear(data); + ImGui::CloseCurrentPopup(); + } + ImGui::HelpTooltip("Clear: Remove all items of the array"); + ImGui::EndPopup(); + } + } + else if (p::HasTypeFlags(typeId, p::TF_Struct)) + { + auto props = GetTypeProperties(typeId); + open = InspectBeginCategory(label, props.Size() <= 0); + } + + 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")) + { + 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); + 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() + { + ImGui::TableSetColumnIndex(0); + } + 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 using DrawNodeAccess = TAccessRef; namespace details { @@ -421,7 +748,7 @@ namespace p ImGui::EndMenuBar(); } - if (valid) + if (valid && BeginInspection("##Inspector")) { String componentLabel; for (const auto& poolInstance : GetDebugCtx().GetPools()) @@ -441,24 +768,32 @@ 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)) { - // UI::Indent(); - // auto* dataType = Cast(type); - // if (data && dataType && UI::BeginInspector("EntityInspector")) - //{ - // UI::InspectChildrenProperties({data, dataType}); - // UI::EndInspector(); - // } - // UI::Unindent(); + if (data) + { + ImGui::Indent(); + 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(); + } } } + EndInspection(); } else { @@ -700,7 +1035,7 @@ namespace p return; } - const auto& typeProperties = GetTypeProperties(type); + const auto& typeProperties = GetOwnTypeProperties(type); static String idText; idText.clear(); @@ -857,12 +1192,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/Misc/PipeImGui.h b/Include/Misc/PipeImGui.h index 64c3b84c..741b0a03 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_ForTooltip | 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_ForTooltip | 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/Pipe/Core/String.h b/Include/Pipe/Core/String.h index 6735c719..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 @@ -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/Include/Pipe/Memory/Alloc.h b/Include/Pipe/Memory/Alloc.h index 186d6477..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(); @@ -23,7 +22,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/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 4db87f6b..6619c2a3 100644 --- a/Include/Pipe/Memory/HeapArena.h +++ b/Include/Pipe/Memory/HeapArena.h @@ -4,25 +4,35 @@ #include "Pipe/Memory/Alloc.h" #include "Pipe/Memory/Arena.h" +#include "Pipe/Memory/MemoryStats.h" + namespace p { class PIPE_API HeapArena : public Arena { + private: + MemoryStats stats; + public: HeapArena() { + stats.name = "Heap Arena"; Interface(); } ~HeapArena() override = default; 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 +40,10 @@ namespace p } void Free(void* ptr, sizet size) { + stats.Remove(ptr, size); p::HeapFree(ptr); } + + 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/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/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) diff --git a/Include/PipeReflect.h b/Include/PipeReflect.h index 8c88a8f9..a1325113 100644 --- a/Include/PipeReflect.h +++ b/Include/PipeReflect.h @@ -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; @@ -337,7 +338,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); @@ -514,7 +518,7 @@ namespace p { T::BuildType(); } - else + else if constexpr (HasExternalBuildType()) { BuildType((const T*)nullptr); } @@ -576,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) \ @@ -754,49 +761,53 @@ P_NATIVE_NAMED(p::HSVColor, "HSVColor") P_NATIVE_NAMED(p::Color, "Color") // Build array types -template -inline void BuildType(const p::TArray*) +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) { - 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 - p::AddTypeFlags(p::TF_Container); - p::SetTypeOps(&ops); -}; + // clang-format on + p::AddTypeFlags(p::TF_Container); + p::SetTypeOps(&ops); + }; +} // namespace p #pragma endregion PipeTypesSupport diff --git a/Src/Memory/Alloc.cpp b/Src/Memory/Alloc.cpp index e9543c93..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 @@ -16,17 +15,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 +30,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 +61,6 @@ namespace p currentArena = &arena; } - MemoryStats* GetHeapStats() - { - static MemoryStats heapStats; - return &heapStats; - } - void* Alloc(Arena& arena, sizet size) { 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/Src/PipeReflect.cpp b/Src/PipeReflect.cpp index 80a50b30..b876d77b 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; @@ -54,7 +55,7 @@ namespace p static TypeRegistry registry; return registry; } - TArray& GetRefectInitCallacks() + TArray& GetRefectInitCallbacks() { static TArray onInitCallbacks{GetHeapArena()}; return onInitCallbacks; @@ -154,7 +155,7 @@ namespace p return false; } - for (auto callback : GetRefectInitCallacks()) + for (auto callback : GetRefectInitCallbacks()) { if (callback) { @@ -169,7 +170,7 @@ namespace p { if (callback) { - GetRefectInitCallacks().Add(callback); + GetRefectInitCallbacks().Add(callback); } } @@ -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) @@ -261,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; } @@ -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; } @@ -301,17 +309,40 @@ 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) { P_CheckEditingType; - GetRegistry().parentIds[currentEdit.index] = parentId; + auto& reg = GetRegistry(); + reg.parentIds[currentEdit.index] = parentId; } void SetTypeSize(sizet size) @@ -347,8 +378,8 @@ namespace p void AddTypeProperty(const TypeProperty& property) { P_CheckEditingType; - auto& properties = GetRegistry().properties[currentEdit.index]; - properties.Add(property); + auto& ownProperties = GetRegistry().ownProperties[currentEdit.index]; + ownProperties.Add(property); } void SetTypeOps(const TypeOps* operations) 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/Core/String.spec.cpp b/Tests/Core/String.spec.cpp index 710aa6e6..ab1ef4e9 100644 --- a/Tests/Core/String.spec.cpp +++ b/Tests/Core/String.spec.cpp @@ -82,6 +82,30 @@ 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] == 0xd834, Is().True()); + AssertThat(utf16result[3] == 0xdd1e, Is().True()); + }); + 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)); + }); }); }); }); 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)); });*/ }); 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")); }); }); 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()); + }); }); });