mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 08:17:45 +00:00
LibWeb: Stop putting the FormattingState nodes in a slow hash map
Instead, put them in a Vector<OwnPtr<NodeState>>. Each layout node has a unique index into the vector. It's a simple serial ID assigned during layout tree construction. Every new layout restarts the sequence at 0 for the next ICB. This is a huge layout speed improvement on all content.
This commit is contained in:
parent
f6a97ff7d5
commit
0cacaf025d
6 changed files with 45 additions and 45 deletions
|
@ -606,11 +606,13 @@ void Document::update_layout()
|
|||
auto viewport_rect = browsing_context()->viewport_rect();
|
||||
|
||||
if (!m_layout_root) {
|
||||
m_next_layout_node_serial_id = 0;
|
||||
Layout::TreeBuilder tree_builder;
|
||||
m_layout_root = static_ptr_cast<Layout::InitialContainingBlock>(tree_builder.build(*this));
|
||||
}
|
||||
|
||||
Layout::FormattingState formatting_state;
|
||||
formatting_state.nodes.resize(layout_node_count());
|
||||
Layout::BlockFormattingContext root_formatting_context(formatting_state, *m_layout_root, nullptr);
|
||||
|
||||
auto& icb = static_cast<Layout::InitialContainingBlock&>(*m_layout_root);
|
||||
|
|
|
@ -61,6 +61,9 @@ public:
|
|||
|
||||
virtual ~Document() override;
|
||||
|
||||
size_t next_layout_node_serial_id(Badge<Layout::Node>) { return m_next_layout_node_serial_id++; }
|
||||
size_t layout_node_count() const { return m_next_layout_node_serial_id; }
|
||||
|
||||
String cookie(Cookie::Source = Cookie::Source::NonHttp);
|
||||
void set_cookie(String const&, Cookie::Source = Cookie::Source::NonHttp);
|
||||
|
||||
|
@ -391,6 +394,8 @@ private:
|
|||
|
||||
unsigned m_referencing_node_count { 0 };
|
||||
|
||||
size_t m_next_layout_node_serial_id { 0 };
|
||||
|
||||
OwnPtr<CSS::StyleComputer> m_style_computer;
|
||||
RefPtr<CSS::StyleSheetList> m_style_sheets;
|
||||
RefPtr<Node> m_hovered_node;
|
||||
|
|
|
@ -12,48 +12,37 @@ namespace Web::Layout {
|
|||
|
||||
FormattingState::NodeState& FormattingState::get_mutable(NodeWithStyleAndBoxModelMetrics const& box)
|
||||
{
|
||||
if (m_lookup_cache.box == &box && m_lookup_cache.is_mutable)
|
||||
return *m_lookup_cache.state;
|
||||
|
||||
auto& node_state = [&]() -> NodeState& {
|
||||
if (auto it = nodes.find(&box); it != nodes.end())
|
||||
return *it->value;
|
||||
auto serial_id = box.serial_id();
|
||||
if (nodes[serial_id])
|
||||
return *nodes[serial_id];
|
||||
|
||||
for (auto const* ancestor = m_parent; ancestor; ancestor = ancestor->m_parent) {
|
||||
if (auto it = ancestor->nodes.find(&box); it != ancestor->nodes.end()) {
|
||||
auto cow_node_state = adopt_own(*new NodeState(*it->value));
|
||||
if (ancestor->nodes[serial_id]) {
|
||||
auto cow_node_state = adopt_own(*new NodeState(*ancestor->nodes[serial_id]));
|
||||
auto* cow_node_state_ptr = cow_node_state.ptr();
|
||||
nodes.set(&box, move(cow_node_state));
|
||||
nodes[serial_id] = move(cow_node_state);
|
||||
return *cow_node_state_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
return *nodes.ensure(&box, [] { return adopt_own(*new NodeState); });
|
||||
}();
|
||||
|
||||
m_lookup_cache = LookupCache { .box = &box, .state = &node_state, .is_mutable = true };
|
||||
|
||||
return node_state;
|
||||
nodes[serial_id] = adopt_own(*new NodeState);
|
||||
nodes[serial_id]->node = const_cast<NodeWithStyleAndBoxModelMetrics*>(&box);
|
||||
return *nodes[serial_id];
|
||||
}
|
||||
|
||||
FormattingState::NodeState const& FormattingState::get(NodeWithStyleAndBoxModelMetrics const& box) const
|
||||
{
|
||||
if (m_lookup_cache.box == &box)
|
||||
return *m_lookup_cache.state;
|
||||
|
||||
auto& node_state = [&]() -> NodeState const& {
|
||||
if (auto it = nodes.find(&box); it != nodes.end())
|
||||
return *it->value;
|
||||
auto serial_id = box.serial_id();
|
||||
if (nodes[serial_id])
|
||||
return *nodes[serial_id];
|
||||
|
||||
for (auto* ancestor = m_parent; ancestor; ancestor = ancestor->m_parent) {
|
||||
if (auto it = ancestor->nodes.find(&box); it != ancestor->nodes.end())
|
||||
return *it->value;
|
||||
if (ancestor->nodes[serial_id])
|
||||
return *ancestor->nodes[serial_id];
|
||||
}
|
||||
return *const_cast<FormattingState&>(*this).nodes.ensure(&box, [] { return adopt_own(*new NodeState); });
|
||||
}();
|
||||
|
||||
const_cast<FormattingState*>(this)->m_lookup_cache = LookupCache { .box = &box, .state = const_cast<NodeState*>(&node_state), .is_mutable = false };
|
||||
return node_state;
|
||||
const_cast<FormattingState*>(this)->nodes[serial_id] = adopt_own(*new NodeState);
|
||||
const_cast<FormattingState*>(this)->nodes[serial_id]->node = const_cast<NodeWithStyleAndBoxModelMetrics*>(&box);
|
||||
return *nodes[serial_id];
|
||||
}
|
||||
|
||||
void FormattingState::commit()
|
||||
|
@ -63,9 +52,11 @@ void FormattingState::commit()
|
|||
|
||||
HashTable<Layout::TextNode*> text_nodes;
|
||||
|
||||
for (auto& it : nodes) {
|
||||
auto& node = const_cast<Layout::NodeWithStyleAndBoxModelMetrics&>(*it.key);
|
||||
auto& node_state = *it.value;
|
||||
for (auto& node_state_ptr : nodes) {
|
||||
if (!node_state_ptr)
|
||||
continue;
|
||||
auto& node_state = *node_state_ptr;
|
||||
auto& node = *node_state.node;
|
||||
|
||||
// Transfer box model metrics.
|
||||
node.box_model().inset = { node_state.inset_top, node_state.inset_right, node_state.inset_bottom, node_state.inset_left };
|
||||
|
|
|
@ -30,6 +30,7 @@ struct FormattingState {
|
|||
: m_parent(parent)
|
||||
, m_root(find_root())
|
||||
{
|
||||
nodes.resize(m_root.nodes.size());
|
||||
}
|
||||
|
||||
FormattingState const& find_root() const
|
||||
|
@ -41,6 +42,8 @@ struct FormattingState {
|
|||
}
|
||||
|
||||
struct NodeState {
|
||||
Layout::NodeWithStyleAndBoxModelMetrics* node { nullptr };
|
||||
|
||||
float content_width { 0 };
|
||||
float content_height { 0 };
|
||||
Gfx::FloatPoint offset;
|
||||
|
@ -106,7 +109,7 @@ struct FormattingState {
|
|||
// NOTE: get() will not CoW the NodeState.
|
||||
NodeState const& get(NodeWithStyleAndBoxModelMetrics const&) const;
|
||||
|
||||
HashMap<NodeWithStyleAndBoxModelMetrics const*, NonnullOwnPtr<NodeState>> nodes;
|
||||
Vector<OwnPtr<NodeState>> nodes;
|
||||
|
||||
// We cache intrinsic sizes once determined, as they will not change over the course of a full layout.
|
||||
// This avoids computing them several times while performing flex layout.
|
||||
|
@ -123,13 +126,6 @@ struct FormattingState {
|
|||
|
||||
FormattingState const* m_parent { nullptr };
|
||||
FormattingState const& m_root;
|
||||
|
||||
struct LookupCache {
|
||||
NodeWithStyleAndBoxModelMetrics const* box { nullptr };
|
||||
NodeState* state { nullptr };
|
||||
bool is_mutable { false };
|
||||
};
|
||||
LookupCache m_lookup_cache;
|
||||
};
|
||||
|
||||
Gfx::FloatRect absolute_content_rect(Box const&, FormattingState const&);
|
||||
|
|
|
@ -23,6 +23,8 @@ Node::Node(DOM::Document& document, DOM::Node* node)
|
|||
: m_document(document)
|
||||
, m_dom_node(node)
|
||||
{
|
||||
m_serial_id = m_document->next_layout_node_serial_id({});
|
||||
|
||||
if (m_dom_node)
|
||||
m_dom_node->set_layout_node({}, this);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ class Node : public TreeNode<Node> {
|
|||
public:
|
||||
virtual ~Node();
|
||||
|
||||
size_t serial_id() const { return m_serial_id; }
|
||||
|
||||
bool is_anonymous() const { return !m_dom_node; }
|
||||
const DOM::Node* dom_node() const { return m_dom_node; }
|
||||
DOM::Node* dom_node() { return m_dom_node; }
|
||||
|
@ -141,6 +143,8 @@ private:
|
|||
RefPtr<DOM::Node> m_dom_node;
|
||||
RefPtr<Painting::Paintable> m_paintable;
|
||||
|
||||
size_t m_serial_id { 0 };
|
||||
|
||||
bool m_inline { false };
|
||||
bool m_has_style { false };
|
||||
bool m_visible { true };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue