Browse Source

Add debug tracking for layout in independent formatting contexts

Michael Ragazzon 6 months ago
parent
commit
1ad28c6ada

+ 6 - 1
Source/Core/ControlledLifetimeResource.h

@@ -65,7 +65,12 @@ public:
 		pointer = nullptr;
 		pointer = nullptr;
 	}
 	}
 
 
-	T* operator->()
+	T& operator*() const
+	{
+		RMLUI_ASSERTMSG(pointer, "Resource used before it was initialized, or after it was shut down.");
+		return *pointer;
+	}
+	T* operator->() const
 	{
 	{
 		RMLUI_ASSERTMSG(pointer, "Resource used before it was initialized, or after it was shut down.");
 		RMLUI_ASSERTMSG(pointer, "Resource used before it was initialized, or after it was shut down.");
 		return pointer;
 		return pointer;

+ 2 - 0
Source/Core/Layout/CMakeLists.txt

@@ -14,6 +14,8 @@ target_sources(rmlui_core PRIVATE
 	"${CMAKE_CURRENT_SOURCE_DIR}/FloatedBoxSpace.h"
 	"${CMAKE_CURRENT_SOURCE_DIR}/FloatedBoxSpace.h"
 	"${CMAKE_CURRENT_SOURCE_DIR}/FormattingContext.cpp"
 	"${CMAKE_CURRENT_SOURCE_DIR}/FormattingContext.cpp"
 	"${CMAKE_CURRENT_SOURCE_DIR}/FormattingContext.h"
 	"${CMAKE_CURRENT_SOURCE_DIR}/FormattingContext.h"
+	"${CMAKE_CURRENT_SOURCE_DIR}/FormattingContextDebug.cpp"
+	"${CMAKE_CURRENT_SOURCE_DIR}/FormattingContextDebug.h"
 	"${CMAKE_CURRENT_SOURCE_DIR}/InlineBox.cpp"
 	"${CMAKE_CURRENT_SOURCE_DIR}/InlineBox.cpp"
 	"${CMAKE_CURRENT_SOURCE_DIR}/InlineBox.h"
 	"${CMAKE_CURRENT_SOURCE_DIR}/InlineBox.h"
 	"${CMAKE_CURRENT_SOURCE_DIR}/InlineContainer.cpp"
 	"${CMAKE_CURRENT_SOURCE_DIR}/InlineContainer.cpp"

+ 45 - 5
Source/Core/Layout/FormattingContext.cpp

@@ -31,8 +31,11 @@
 #include "../../../Include/RmlUi/Core/Element.h"
 #include "../../../Include/RmlUi/Core/Element.h"
 #include "../../../Include/RmlUi/Core/Profiling.h"
 #include "../../../Include/RmlUi/Core/Profiling.h"
 #include "BlockFormattingContext.h"
 #include "BlockFormattingContext.h"
+#include "ContainerBox.h"
 #include "FlexFormattingContext.h"
 #include "FlexFormattingContext.h"
+#include "FormattingContextDebug.h"
 #include "LayoutBox.h"
 #include "LayoutBox.h"
+#include "LayoutDetails.h"
 #include "ReplacedFormattingContext.h"
 #include "ReplacedFormattingContext.h"
 #include "TableFormattingContext.h"
 #include "TableFormattingContext.h"
 
 
@@ -66,22 +69,59 @@ FormattingContextType FormattingContext::GetFormattingContextType(Element* eleme
 UniquePtr<LayoutBox> FormattingContext::FormatIndependent(ContainerBox* parent_container, Element* element, const Box* override_initial_box,
 UniquePtr<LayoutBox> FormattingContext::FormatIndependent(ContainerBox* parent_container, Element* element, const Box* override_initial_box,
 	FormattingContextType default_context)
 	FormattingContextType default_context)
 {
 {
+	RMLUI_ASSERT(parent_container && element);
 	RMLUI_ZoneScopedC(0xAFAFAF);
 	RMLUI_ZoneScopedC(0xAFAFAF);
 
 
 	FormattingContextType type = GetFormattingContextType(element);
 	FormattingContextType type = GetFormattingContextType(element);
 	if (type == FormattingContextType::None)
 	if (type == FormattingContextType::None)
 		type = default_context;
 		type = default_context;
 
 
+#ifdef RMLUI_DEBUG
+	auto* debug_tracker = FormatIndependentDebugTracker::GetIf();
+	FormatIndependentDebugTracker::Entry* tracker_entry = nullptr;
+	if (debug_tracker && type != FormattingContextType::None)
+	{
+		tracker_entry = &debug_tracker->entries.emplace_back(FormatIndependentDebugTracker::Entry{
+			debug_tracker->current_stack_level,
+			parent_container->GetElement() ? parent_container->GetElement()->GetAddress() : "",
+			parent_container->GetAbsolutePositioningContainingBlockElementForDebug()
+				? parent_container->GetAbsolutePositioningContainingBlockElementForDebug()->GetAddress()
+				: "",
+			parent_container->GetContainingBlockSize(Style::Position::Static),
+			parent_container->GetContainingBlockSize(Style::Position::Absolute),
+			element->GetAddress(),
+			override_initial_box ? Optional<Box>{*override_initial_box} : std::nullopt,
+			type,
+		});
+		debug_tracker->current_stack_level += 1;
+	}
+#endif
+
+	UniquePtr<LayoutBox> layout_box;
 	switch (type)
 	switch (type)
 	{
 	{
-	case FormattingContextType::Block: return BlockFormattingContext::Format(parent_container, element, override_initial_box);
-	case FormattingContextType::Table: return TableFormattingContext::Format(parent_container, element, override_initial_box);
-	case FormattingContextType::Flex: return FlexFormattingContext::Format(parent_container, element, override_initial_box);
-	case FormattingContextType::Replaced: return ReplacedFormattingContext::Format(parent_container, element, override_initial_box);
+	case FormattingContextType::Block: layout_box = BlockFormattingContext::Format(parent_container, element, override_initial_box); break;
+	case FormattingContextType::Table: layout_box = TableFormattingContext::Format(parent_container, element, override_initial_box); break;
+	case FormattingContextType::Flex: layout_box = FlexFormattingContext::Format(parent_container, element, override_initial_box); break;
+	case FormattingContextType::Replaced: layout_box = ReplacedFormattingContext::Format(parent_container, element, override_initial_box); break;
 	case FormattingContextType::None: break;
 	case FormattingContextType::None: break;
 	}
 	}
 
 
-	return nullptr;
+#ifdef RMLUI_DEBUG
+	if (tracker_entry)
+	{
+		debug_tracker->current_stack_level -= 1;
+		if (layout_box)
+		{
+			tracker_entry->layout = Optional<FormatIndependentDebugTracker::Entry::LayoutResults>({
+				layout_box->GetVisibleOverflowSize(),
+				layout_box->GetIfBox() ? Optional<Box>{*layout_box->GetIfBox()} : std::nullopt,
+			});
+		}
+	}
+#endif
+
+	return layout_box;
 }
 }
 
 
 } // namespace Rml
 } // namespace Rml

+ 157 - 0
Source/Core/Layout/FormattingContextDebug.cpp

@@ -0,0 +1,157 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2025 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "FormattingContextDebug.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+
+namespace Rml {
+#ifdef RMLUI_DEBUG
+
+static FormatIndependentDebugTracker* g_debug_format_independent_tracker = nullptr;
+
+static std::string repeat(const std::string& input, int count)
+{
+	if (count <= 0)
+		return {};
+	std::string result;
+	result.reserve(input.size() * size_t(count));
+	for (size_t i = 0; i < size_t(count); ++i)
+		result += input;
+	return result;
+}
+
+FormatIndependentDebugTracker::FormatIndependentDebugTracker()
+{
+	RMLUI_ASSERTMSG(!g_debug_format_independent_tracker, "An instance of FormatIndependentDebugTracker already exists");
+	g_debug_format_independent_tracker = this;
+}
+
+FormatIndependentDebugTracker::~FormatIndependentDebugTracker()
+{
+	RMLUI_ASSERT(g_debug_format_independent_tracker == this);
+	RMLUI_ASSERT(current_stack_level == 0);
+	g_debug_format_independent_tracker = nullptr;
+}
+
+FormatIndependentDebugTracker* FormatIndependentDebugTracker::GetIf()
+{
+	return g_debug_format_independent_tracker;
+}
+
+void FormatIndependentDebugTracker::Reset()
+{
+	*this = {};
+}
+
+String FormatIndependentDebugTracker::ToString() const
+{
+	String result;
+
+	for (const auto& entry : entries)
+	{
+		const std::string newline = '\n' + repeat("|   ", entry.level);
+		result += '\n' + repeat("+ - ", entry.level) + entry.element;
+		result += newline + "|   Containing block: " + Rml::ToString(entry.containing_block.x) + " x " + Rml::ToString(entry.containing_block.y) +
+			". " + entry.parent_container_element;
+		result += newline + "|   Absolute positioning containing block: " + Rml::ToString(entry.absolute_positioning_containing_block.x) + " x " +
+			Rml::ToString(entry.absolute_positioning_containing_block.y) + ". " + entry.absolute_positioning_containing_block_element;
+
+		result += newline + "|   Formatting context: ";
+		switch (entry.type)
+		{
+		case FormattingContextType::Block: result += "Block"; break;
+		case FormattingContextType::Table: result += "Table"; break;
+		case FormattingContextType::Flex: result += "Flex"; break;
+		case FormattingContextType::Replaced: result += "Replaced"; break;
+		case FormattingContextType::None: result += "None"; break;
+		default: result += "Unknown"; break;
+		}
+
+		if (entry.override_box)
+		{
+			const Box& box = *entry.override_box;
+			result += newline + "|   Override box: ";
+			result += Rml::ToString(box.GetSize().x) + " x " + Rml::ToString(box.GetSize().y);
+			result += " (outer size: " + Rml::ToString(box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin)) + " x " +
+				Rml::ToString(box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin)) + ")";
+		}
+		else
+		{
+			result += newline + "|   Override box: none";
+		}
+
+		if (entry.layout)
+		{
+			result += newline + "|   Layout results:";
+			result += newline + "|       Visible overflow: " + Rml::ToString(entry.layout->visible_overflow_size.x) + " x " +
+				Rml::ToString(entry.layout->visible_overflow_size.y);
+
+			if (entry.layout->box)
+			{
+				const Box& box = *entry.layout->box;
+				result += newline + "|       Layout box: ";
+				result += Rml::ToString(box.GetSize().x) + " x " + Rml::ToString(box.GetSize().y);
+				result += " (outer size: " + Rml::ToString(box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin)) + " x " +
+					Rml::ToString(box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin)) + ")";
+			}
+			else
+			{
+				result += newline + "|       Layout box: none";
+			}
+		}
+		else
+		{
+			result += newline + "|   Layout results: none";
+		}
+	}
+
+	return result;
+}
+
+void FormatIndependentDebugTracker::LogMessage() const
+{
+	Log::Message(Log::LT_INFO, "%s", ToString().c_str());
+}
+
+int FormatIndependentDebugTracker::CountCachedEntries() const
+{
+	return (int)std::count_if(entries.begin(), entries.end(), [](const auto& entry) { return entry.layout.has_value(); });
+}
+
+int FormatIndependentDebugTracker::CountFormattedEntries() const
+{
+	return (int)entries.size() - CountCachedEntries();
+}
+
+int FormatIndependentDebugTracker::CountEntries() const
+{
+	return (int)entries.size();
+}
+
+#endif // RMLUI_DEBUG
+} // namespace Rml

+ 75 - 0
Source/Core/Layout/FormattingContextDebug.h

@@ -0,0 +1,75 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2025 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RMLUI_CORE_LAYOUT_FORMATTINGCONTEXTDEBUG_H
+#define RMLUI_CORE_LAYOUT_FORMATTINGCONTEXTDEBUG_H
+
+#include "../../../Include/RmlUi/Core/Box.h"
+#include "../../../Include/RmlUi/Core/Types.h"
+#include "FormattingContext.h"
+
+namespace Rml {
+#ifdef RMLUI_DEBUG
+
+class FormatIndependentDebugTracker {
+public:
+	FormatIndependentDebugTracker();
+	~FormatIndependentDebugTracker();
+	static FormatIndependentDebugTracker* GetIf();
+
+	struct Entry {
+		int level = 0;
+		String parent_container_element;
+		String absolute_positioning_containing_block_element;
+		Vector2f containing_block;
+		Vector2f absolute_positioning_containing_block;
+		String element;
+		Optional<Box> override_box;
+		FormattingContextType type;
+		struct LayoutResults {
+			Vector2f visible_overflow_size;
+			Optional<Box> box;
+		};
+		Optional<LayoutResults> layout;
+	};
+
+	List<Entry> entries;
+	int current_stack_level = 0;
+
+	void Reset();
+
+	String ToString() const;
+	void LogMessage() const;
+	int CountEntries() const;
+	int CountCachedEntries() const;
+	int CountFormattedEntries() const;
+};
+
+#endif // RMLUI_DEBUG
+} // namespace Rml
+#endif