From aa72ebf7aa88a23ddbef0cfad0838fa8f096c497 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 28 Feb 2022 12:32:23 +0100 Subject: [PATCH] LibWeb: Use copy-on-write to make cloning a FormattingState much cheaper Instead of making a full copy of every NodeState when cloning a FormattingState, we make NodeState ref-counted and implement a basic copy-on-write mechanism. FormattingState::get_mutable() now makes a deep copy of the NodeState when first accessed *if* it is shared with other FormattingStates. --- .../LibWeb/Layout/FormattingState.cpp | 17 +++++++++ .../Libraries/LibWeb/Layout/FormattingState.h | 37 ++++++++++--------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Userland/Libraries/LibWeb/Layout/FormattingState.cpp b/Userland/Libraries/LibWeb/Layout/FormattingState.cpp index 98c72a0291..8c7a464f66 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingState.cpp +++ b/Userland/Libraries/LibWeb/Layout/FormattingState.cpp @@ -9,6 +9,23 @@ namespace Web::Layout { +FormattingState::NodeState& FormattingState::get_mutable(NodeWithStyleAndBoxModelMetrics const& box) +{ + auto state = nodes.ensure(&box, [] { return adopt_ref(*new NodeState); }); + // CoW if ref_count > 2 (1 for the entry in `this->nodes`, 1 for the `state` local in this function) + if (state->ref_count > 2) { + state = adopt_ref(*new NodeState { *state }); + state->ref_count = 1; + nodes.set(&box, state); + } + return state; +} + +FormattingState::NodeState const& FormattingState::get(NodeWithStyleAndBoxModelMetrics const& box) const +{ + return *const_cast(*this).nodes.ensure(&box, [] { return adopt_ref(*new NodeState); }); +} + void FormattingState::commit() { for (auto& it : nodes) { diff --git a/Userland/Libraries/LibWeb/Layout/FormattingState.h b/Userland/Libraries/LibWeb/Layout/FormattingState.h index 1d2dfa0be2..12e3f9264c 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingState.h +++ b/Userland/Libraries/LibWeb/Layout/FormattingState.h @@ -62,30 +62,31 @@ struct FormattingState { overflow_data = Layout::Box::OverflowData {}; return *overflow_data; } + + // NOTE: NodeState is ref-counted and accessed via copy-on-write helpers below. + size_t ref_count { 1 }; + void ref() + { + VERIFY(ref_count); + ++ref_count; + } + void unref() + { + VERIFY(ref_count); + if (!--ref_count) + delete this; + } }; void commit(); - FormattingState clone() const - { - FormattingState new_state; - for (auto& it : nodes) { - new_state.nodes.set(it.key, make(*it.value)); - } - return new_state; - } + // NOTE: get_mutable() will CoW the NodeState if it's shared with another FormattingContext. + NodeState& get_mutable(NodeWithStyleAndBoxModelMetrics const&); - NodeState& get_mutable(NodeWithStyleAndBoxModelMetrics const& box) - { - return *nodes.ensure(&box, [] { return make(); }); - } + // NOTE: get() will not CoW the NodeState. + NodeState const& get(NodeWithStyleAndBoxModelMetrics const&) const; - NodeState const& get(NodeWithStyleAndBoxModelMetrics const& box) const - { - return const_cast(*this).get_mutable(box); - } - - HashMap> nodes; + HashMap> nodes; }; Gfx::FloatRect absolute_content_rect(Box const&, FormattingState const&);