소스 검색

Refactor partial layout cache into layout engine

Michael Ragazzon 4 달 전
부모
커밋
20f957e8cc
4개의 변경된 파일108개의 추가작업 그리고 82개의 파일을 삭제
  1. 5 74
      Source/Core/ElementDocument.cpp
  2. 1 1
      Source/Core/ElementUtilities.cpp
  3. 100 4
      Source/Core/Layout/LayoutEngine.cpp
  4. 2 3
      Source/Core/Layout/LayoutEngine.h

+ 5 - 74
Source/Core/ElementDocument.cpp

@@ -40,7 +40,6 @@
 #include "EventDispatcher.h"
 #include "Layout/LayoutDetails.h"
 #include "Layout/LayoutEngine.h"
-#include "Layout/LayoutNode.h"
 #include "StreamFile.h"
 #include "StyleSheetFactory.h"
 #include "Template.h"
@@ -532,85 +531,17 @@ void ElementDocument::UpdateLayout()
 		RMLUI_ZoneScoped;
 		RMLUI_ZoneText(source_url);
 
-		const bool allow_layout_cache = !HasAttribute("rmlui-disable-layout-cache");
-
-		constexpr bool debug_logging = false;
-		if (debug_logging)
-			Log::Message(Log::LT_INFO, "UpdateLayout start: %s", GetAddress().c_str());
-
-		bool force_full_document_layout = !allow_layout_cache;
-		bool any_layout_updates = false;
-
-		if (!force_full_document_layout)
-		{
-			ElementUtilities::BreadthFirstSearch(this, [&](Element* element) {
-				if (element->GetDisplay() == Style::Display::None)
-					return ElementUtilities::CallbackControlFlow::SkipChildren;
-
-				LayoutNode* layout_node = element->GetLayoutNode();
-				if (!layout_node->IsDirty())
-					return ElementUtilities::CallbackControlFlow::Continue;
+		Vector2f containing_block;
+		if (Element* parent = GetParentNode())
+			containing_block = parent->GetBox().GetSize();
 
-				if (!layout_node->IsLayoutBoundary())
-				{
-					// Normally the dirty layout should have propagated to the closest layout boundary during
-					// Element::Update(), which then invokes formatting on that boundary element, and which again clears the
-					// dirty layout on this element. This didn't happen here, which may be caused by modifying the layout
-					// during layouting itself. For now, we simply skip this element and keep going. The element should
-					// normally be properly layed-out on the next context update.
-					// TODO: Might want to investigate this further.
-					if (debug_logging)
-						Log::Message(Log::LT_INFO, "Dirty layout on non-layout boundary element: %s", element->GetAddress().c_str());
-					return ElementUtilities::CallbackControlFlow::Continue;
-				}
-
-				any_layout_updates = true;
-
-				const Optional<CommittedLayout>& committed_layout = layout_node->GetCommittedLayout();
-				if (!committed_layout)
-				{
-					if (element != this && debug_logging)
-						Log::Message(Log::LT_INFO, "Forcing full layout update due to missing committed layout on element: %s",
-							element->GetAddress().c_str());
-					force_full_document_layout = true;
-					// TODO: When is the committed layout dirtied?
-					return ElementUtilities::CallbackControlFlow::Break;
-				}
-
-				if (element != this && debug_logging)
-					Log::Message(Log::LT_INFO, "Doing partial layout update on element: %s", element->GetAddress().c_str());
-
-				// TODO: In some cases, we need to check if size changed, such that we need to do a layout update in its parent.
-				LayoutEngine::FormatElement(element, committed_layout->containing_block_size,
-					committed_layout->absolutely_positioning_containing_block_size, allow_layout_cache);
-
-				// TODO: A bit ugly
-				element->UpdateRelativeOffsetFromInsetConstraints();
-
-				return ElementUtilities::CallbackControlFlow::SkipChildren;
-			});
-		}
-
-		if (force_full_document_layout)
-		{
-			Vector2f containing_block;
-			if (Element* parent = GetParentNode())
-				containing_block = parent->GetBox().GetSize();
-
-			LayoutEngine::FormatElement(this, containing_block, containing_block, allow_layout_cache);
-			// TODO: A bit ugly
-			this->UpdateRelativeOffsetFromInsetConstraints();
-		}
+		const bool allow_layout_cache = !HasAttribute("rmlui-disable-layout-cache");
 
-		if (!any_layout_updates && debug_logging)
-			Log::Message(Log::LT_INFO, "Didn't make any layout updates on dirty document: %s", GetAddress().c_str());
+		LayoutEngine::FormatElement(this, containing_block, allow_layout_cache);
 
 		// Ignore dirtied layout during document formatting. Layouting must not require re-iteration.
 		// In particular, scrollbars being enabled may set the dirty flag, but this case is already handled within the layout engine.
 		layout_dirty = false;
-
-		if (debug_logging)
-			Log::Message(Log::LT_INFO, "Document layout: Clear dirty on %s", GetAddress().c_str());
 	}
 }
 

+ 1 - 1
Source/Core/ElementUtilities.cpp

@@ -304,7 +304,7 @@ bool ElementUtilities::GetBoundingBox(Rectanglef& out_rectangle, Element* elemen
 
 void ElementUtilities::FormatElement(Element* element, Vector2f containing_block)
 {
-	LayoutEngine::FormatElement(element, containing_block, containing_block);
+	LayoutEngine::FormatElement(element, containing_block, false);
 }
 
 void ElementUtilities::BuildBox(Box& box, Vector2f containing_block, Element* element, bool inline_element)

+ 100 - 4
Source/Core/Layout/LayoutEngine.cpp

@@ -28,18 +28,22 @@
 
 #include "LayoutEngine.h"
 #include "../../../Include/RmlUi/Core/Element.h"
+#include "../../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../../Include/RmlUi/Core/Log.h"
 #include "../../../Include/RmlUi/Core/Profiling.h"
 #include "ContainerBox.h"
 #include "FormattingContext.h"
+#include "LayoutDetails.h"
+#include "LayoutNode.h"
 
 namespace Rml {
 
-void LayoutEngine::FormatElement(Element* element, Vector2f containing_block, Vector2f absolutely_positioning_containing_block, bool allow_cache)
+static void FormatElementImpl(Element* element, Vector2f containing_block, Vector2f absolutely_positioning_containing_block,
+	const FormattingMode& formatting_mode)
 {
-	RMLUI_ASSERT(element && containing_block.x >= 0 && containing_block.y >= 0);
+	RMLUI_ZoneScoped;
 
-	RootBox absolute_root(Box(absolutely_positioning_containing_block), FormattingMode{FormattingMode::Constraint::None, allow_cache, allow_cache});
+	RootBox absolute_root(Box(absolutely_positioning_containing_block), formatting_mode);
 	RootBox root(Box(containing_block), &absolute_root);
 
 	auto layout_box = FormattingContext::FormatIndependent(&root, element, nullptr, FormattingContextType::Block);
@@ -54,10 +58,102 @@ void LayoutEngine::FormatElement(Element* element, Vector2f containing_block, Ve
 		Log::Message(Log::LT_ERROR, "%d absolutely positioned box(es) not closed, while formatting: %s", num_absolute_boxes,
 			element->GetAddress().c_str());
 	}
+}
+
+void LayoutEngine::FormatElement(Element* layout_root, Vector2f containing_block, bool allow_cache)
+{
+	RMLUI_ASSERT(layout_root && containing_block.x >= 0 && containing_block.y >= 0);
+
+	const FormattingMode formatting_mode{FormattingMode::Constraint::None, allow_cache, allow_cache};
+
+	constexpr bool debug_logging = false;
+	if (debug_logging)
+		Log::Message(Log::LT_INFO, "UpdateLayout start: %s", layout_root->GetAddress().c_str());
+
+	struct ElementToFormat {
+		Element* element;
+		Vector2f containing_block_size;
+		Vector2f absolutely_positioning_containing_block_size;
+	};
+	Vector<ElementToFormat> elements;
+
+	bool force_full_document_layout = !allow_cache;
+	if (!force_full_document_layout)
+	{
+		ElementUtilities::BreadthFirstSearch(layout_root, [&](Element* candidate) {
+			if (candidate->GetDisplay() == Style::Display::None)
+				return ElementUtilities::CallbackControlFlow::SkipChildren;
+
+			LayoutNode* layout_node = candidate->GetLayoutNode();
+			if (!layout_node->IsDirty())
+				return ElementUtilities::CallbackControlFlow::Continue;
+
+			if (!layout_node->IsLayoutBoundary())
+			{
+				// Normally the dirty layout should have propagated to the closest layout boundary during
+				// Element::Update(), which then invokes formatting on that boundary element, and which again clears the
+				// dirty layout on this element. This didn't happen here, which may be caused by modifying the layout
+				// during layouting itself. For now, we simply skip this element and keep going. The element should
+				// normally be properly layed-out on the next context update.
+				// TODO: Might want to investigate this further.
+				if (debug_logging)
+					Log::Message(Log::LT_INFO, "Dirty layout on non-layout boundary element: %s", candidate->GetAddress().c_str());
+				return ElementUtilities::CallbackControlFlow::Continue;
+			}
+
+			const Optional<CommittedLayout>& committed_layout = layout_node->GetCommittedLayout();
+			if (!committed_layout)
+			{
+				if (candidate != layout_root && debug_logging)
+					Log::Message(Log::LT_INFO, "Forcing full layout update due to missing committed layout on element: %s",
+						candidate->GetAddress().c_str());
+				force_full_document_layout = true;
+				return ElementUtilities::CallbackControlFlow::Break;
+			}
+
+			elements.push_back(ElementToFormat{
+				candidate,
+				committed_layout->containing_block_size,
+				committed_layout->absolutely_positioning_containing_block_size,
+			});
+
+			return ElementUtilities::CallbackControlFlow::SkipChildren;
+		});
+	}
+
+	if (force_full_document_layout)
+	{
+		elements = {
+			ElementToFormat{
+				layout_root,
+				containing_block,
+				containing_block,
+			},
+		};
+	}
+
+	for (const ElementToFormat& element_to_format : elements)
+	{
+		if (element_to_format.element != layout_root && debug_logging)
+			Log::Message(Log::LT_INFO, "Doing partial layout update on element: %s", element_to_format.element->GetAddress().c_str());
+
+		// TODO: In some cases, we need to check if size changed, such that we need to do a layout update in its parent.
+		FormatElementImpl(element_to_format.element, element_to_format.containing_block_size,
+			element_to_format.absolutely_positioning_containing_block_size, formatting_mode);
+
+		// TODO: A bit ugly
+		element_to_format.element->UpdateRelativeOffsetFromInsetConstraints();
+	}
+
+	if (elements.empty() && debug_logging)
+		Log::Message(Log::LT_INFO, "Didn't make any layout updates on dirty document: %s", layout_root->GetAddress().c_str());
+
+	if (debug_logging)
+		Log::Message(Log::LT_INFO, "Document layout: Clear dirty on %s", layout_root->GetAddress().c_str());
 
 	{
 		RMLUI_ZoneScopedN("CommitLayoutRecursive");
-		element->CommitLayoutRecursive();
+		layout_root->CommitLayoutRecursive();
 	}
 }
 

+ 2 - 3
Source/Core/Layout/LayoutEngine.h

@@ -44,11 +44,10 @@ namespace Rml {
 class LayoutEngine {
 public:
 	/// Formats the contents for a root-level element, usually a document, or a replaced element with custom formatting.
-	/// @param[in] element The element to lay out.
+	/// @param[in] layout_root The element to lay out.
 	/// @param[in] containing_block The size of the containing block.
-	/// @param[in] absolutely_positioning_containing_block The size of the absolutely positioning containing block.
 	/// @param[in] allow_cache True to allow skipping layout of elements that match the current layout.
-	static void FormatElement(Element* element, Vector2f containing_block, Vector2f absolutely_positioning_containing_block, bool allow_cache = true);
+	static void FormatElement(Element* layout_root, Vector2f containing_block, bool allow_cache);
 };
 
 } // namespace Rml