From 693c7e7411152c5c5c407b876c3f73774f791b8a Mon Sep 17 00:00:00 2001 From: Alex Lurye Date: Tue, 23 Jan 2024 10:48:39 +0100 Subject: [PATCH] Cache for shrink-to-fit width Reduces amortized complexity of nested flex layout from O(N^2) to O(N). --- Include/RmlUi/Core/Context.h | 5 +++++ Include/RmlUi/Core/Element.h | 17 +++++++++++++++++ Include/RmlUi/Core/Math.h | 4 ++++ Source/Core/Context.cpp | 6 ++++++ Source/Core/Element.cpp | 27 +++++++++++++++++++++++++++ Source/Core/Layout/LayoutDetails.cpp | 8 ++++++++ Source/Core/Math.cpp | 6 ++++++ 7 files changed, 73 insertions(+) diff --git a/Include/RmlUi/Core/Context.h b/Include/RmlUi/Core/Context.h index 98b3c3709..c0114d3b7 100644 --- a/Include/RmlUi/Core/Context.h +++ b/Include/RmlUi/Core/Context.h @@ -299,6 +299,10 @@ class RMLUICORE_API Context : public ScriptInterface { /// @return Time until next update is expected. double GetNextUpdateDelay() const; + /// Get current frame number. Frame number is incremented every time Update() is called. + /// @return Current frame number. + int GetFrameNumber() const; + protected: void Release() override; @@ -307,6 +311,7 @@ class RMLUICORE_API Context : public ScriptInterface { Vector2i dimensions; float density_independent_pixel_ratio; String documents_base_tag = "body"; + int frame_number = 0; SmallUnorderedSet active_themes; diff --git a/Include/RmlUi/Core/Element.h b/Include/RmlUi/Core/Element.h index bb76f3ac0..87da3e750 100644 --- a/Include/RmlUi/Core/Element.h +++ b/Include/RmlUi/Core/Element.h @@ -599,6 +599,19 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr cached_shrink_to_fit_widths; + int cached_shrink_to_fit_widths_frame_number = 0; + friend class Rml::Context; friend class Rml::ElementStyle; friend class Rml::ContainerBox; diff --git a/Include/RmlUi/Core/Math.h b/Include/RmlUi/Core/Math.h index 528169334..57dbd4d1f 100644 --- a/Include/RmlUi/Core/Math.h +++ b/Include/RmlUi/Core/Math.h @@ -43,6 +43,10 @@ class Vector2; using Vector2f = Vector2; using Vector2i = Vector2; +struct Vector2fHash { + auto operator()(const Vector2f& v) const noexcept -> std::size_t; +}; + namespace Math { constexpr float RMLUI_PI = 3.141592653f; diff --git a/Source/Core/Context.cpp b/Source/Core/Context.cpp index 68fd2060f..b7028e9ba 100644 --- a/Source/Core/Context.cpp +++ b/Source/Core/Context.cpp @@ -179,6 +179,7 @@ bool Context::Update() RMLUI_ZoneScoped; next_update_timeout = std::numeric_limits::infinity(); + frame_number++; if (scroll_controller->Update(mouse_position, density_independent_pixel_ratio)) RequestNextUpdate(0); @@ -1411,4 +1412,9 @@ double Context::GetNextUpdateDelay() const return next_update_timeout; } +int Context::GetFrameNumber() const +{ + return frame_number; +} + } // namespace Rml diff --git a/Source/Core/Element.cpp b/Source/Core/Element.cpp index 27b610c8b..c6299192f 100644 --- a/Source/Core/Element.cpp +++ b/Source/Core/Element.cpp @@ -2897,4 +2897,31 @@ void Element::DirtyFontFaceRecursive() GetChild(i)->DirtyFontFaceRecursive(); } +void Element::SetCachedShrinkToFitWidth(float width, Vector2f container_size) +{ + int frame_number = GetContext()->GetFrameNumber(); + if (cached_shrink_to_fit_widths_frame_number != frame_number) + { + cached_shrink_to_fit_widths.clear(); + cached_shrink_to_fit_widths_frame_number = frame_number; + } + cached_shrink_to_fit_widths[container_size] = width; +} + +bool Element::GetCachedShrinkToFitWidth(float& width, Vector2f container_size) const +{ + int frame_number = GetContext()->GetFrameNumber(); + if (cached_shrink_to_fit_widths_frame_number != frame_number) + { + return false; + } + auto it = cached_shrink_to_fit_widths.find(container_size); + if (it == cached_shrink_to_fit_widths.end()) + { + return false; + } + width = it->second; + return true; +} + } // namespace Rml diff --git a/Source/Core/Layout/LayoutDetails.cpp b/Source/Core/Layout/LayoutDetails.cpp index 6deb34767..a9420a20d 100644 --- a/Source/Core/Layout/LayoutDetails.cpp +++ b/Source/Core/Layout/LayoutDetails.cpp @@ -248,6 +248,12 @@ float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_b { RMLUI_ASSERT(element); + float cached_shrink_to_fit_width; + if (element->GetCachedShrinkToFitWidth(cached_shrink_to_fit_width, containing_block)) + { + return cached_shrink_to_fit_width; + } + // @performance Can we lay out the elements directly using a fit-content size mode, instead of fetching the // shrink-to-fit width first? Use a non-definite placeholder for the box content width, and available width as a // maximum constraint. @@ -260,6 +266,7 @@ float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_b const Style::Display display = element->GetDisplay(); if (display == Style::Display::Table || display == Style::Display::InlineTable) { + element->SetCachedShrinkToFitWidth(0.f, containing_block); return 0.f; } @@ -283,6 +290,7 @@ float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_b Math::Max(0.f, containing_block.x - box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding)); shrink_to_fit_width = Math::Min(shrink_to_fit_width, available_width); } + element->SetCachedShrinkToFitWidth(shrink_to_fit_width, containing_block); return shrink_to_fit_width; } diff --git a/Source/Core/Math.cpp b/Source/Core/Math.cpp index a8306c1ed..d76d16419 100644 --- a/Source/Core/Math.cpp +++ b/Source/Core/Math.cpp @@ -280,4 +280,10 @@ namespace Math { } } // namespace Math + +auto Vector2fHash::operator()(const Vector2f& v) const noexcept -> size_t +{ + return std::hash()(v.x) ^ std::hash()(v.y); +} + } // namespace Rml