Chromium Code Reviews| Index: chrome/browser/devtools/serialize_dictionary_forest.cc |
| diff --git a/chrome/browser/devtools/serialize_dictionary_forest.cc b/chrome/browser/devtools/serialize_dictionary_forest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d5319541f9c8f57ce3e1aef5a8670dca5837be71 |
| --- /dev/null |
| +++ b/chrome/browser/devtools/serialize_dictionary_forest.cc |
| @@ -0,0 +1,203 @@ |
| +// Copyright 2017 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "chrome/browser/devtools/serialize_dictionary_forest.h" |
| + |
| +#include <map> |
| +#include <utility> |
| + |
| +#include "base/macros.h" |
| +#include "base/memory/ptr_util.h" |
| + |
| +namespace { |
| + |
| +// Helper class; takes a forest represented by nodes with pointers from |
| +// children to parents and computes a representation allowing to iterate over |
| +// children of a given parent efficiently. |
| +class ChildrenRepresentation { |
| + public: |
| + // A range expression usable in for loops. |
| + class ChildrenRange { |
| + public: |
| + ChildrenRange(DictionaryForestNode** begin, DictionaryForestNode** end) |
| + : begin_(begin), end_(end) {} |
| + |
| + DictionaryForestNode** begin() const { return begin_; } |
| + DictionaryForestNode** end() const { return end_; } |
| + |
| + private: |
| + DictionaryForestNode** const begin_; |
| + DictionaryForestNode** const end_; |
| + }; |
| + |
| + // |forest| must outlive this class. |
| + explicit ChildrenRepresentation(std::vector<DictionaryForestNode>* forest); |
| + |
| + ~ChildrenRepresentation(); |
| + |
| + // Returns a range with all nodes from |forest_| which have |node| as parent. |
| + ChildrenRange For(DictionaryForestNode* node); |
| + |
| + private: |
| + // Just a named pair of offset and length. |
| + struct OffsetLength { |
| + size_t offset = 0; |
| + size_t length = 0; |
| + }; |
| + |
| + // Another named pair: |
| + // |children| stores pointers to nodes in consecutive blocks, such that |
| + // within each block, all nodes have the same parent. |locations| maps |
| + // parents to offset and length of the corresponding block of children in |
| + // |children|. |
| + struct ChildrenLocations { |
| + std::vector<DictionaryForestNode*> children; |
| + std::map<DictionaryForestNode*, OffsetLength> locations; |
| + }; |
| + |
| + // Used by the constructor to compute the edges from parents to children in |
| + // |forest|. |
| + static ChildrenLocations InitFromForest( |
| + std::vector<DictionaryForestNode>* forest); |
| + |
| + ChildrenLocations children_locations_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ChildrenRepresentation); |
| +}; |
| + |
| +ChildrenRepresentation::ChildrenRepresentation( |
| + std::vector<DictionaryForestNode>* forest) |
| + : children_locations_(InitFromForest(forest)) {} |
| + |
| +ChildrenRepresentation::~ChildrenRepresentation() {} |
| + |
| +ChildrenRepresentation::ChildrenRange ChildrenRepresentation::For( |
| + DictionaryForestNode* node) { |
| + OffsetLength ol = children_locations_.locations.at(node); |
| + DictionaryForestNode** first_child = &children_locations_.children[ol.offset]; |
| + return ChildrenRange(first_child, first_child + ol.length); |
|
jdoerrie
2017/04/18 09:57:44
Nit:
return {first_child, first_child + ol.length
vabr (Chromium)
2017/04/19 11:03:17
Acknowledged, but changed due to getting rid of Ch
|
| +} |
| + |
| +ChildrenRepresentation::ChildrenLocations |
| +ChildrenRepresentation::InitFromForest( |
| + std::vector<DictionaryForestNode>* forest) { |
| + ChildrenLocations result; |
| + // There are always less children than all nodes in the |forest|, so resizing |
| + // the vector below is a little wasteful. But it keeps the code simple. |
| + result.children.resize(forest->size()); |
| + |
| + // In the first pass, only count the number of children. |
| + for (DictionaryForestNode& node : *forest) { |
| + DictionaryForestNode* parent = node.parent; |
| + if (!parent) |
| + continue; |
| + ++result.locations[parent].length; |
| + } |
| + |
| + // In the second pass, allocate the offsets and initialize |free_slots|, |
| + // which tells for every node where to put its next child in |
| + // |result.children|. |
| + size_t next_free = 0; |
| + std::map<DictionaryForestNode*, size_t> free_slots; |
| + for (DictionaryForestNode& node : *forest) { |
| + result.locations[&node].offset = next_free; |
| + free_slots[&node] = next_free; |
| + next_free += result.locations[&node].length; |
| + } |
| + |
| + // In the last pass, populate |result.children|. |
| + for (DictionaryForestNode& node : *forest) { |
| + DictionaryForestNode* parent = node.parent; |
| + if (!parent) |
| + continue; |
| + const size_t offset = free_slots[parent]; |
| + ++free_slots[parent]; |
| + result.children[offset] = &node; |
| + } |
| + |
| + return result; |
| +} |
| + |
| +// Appends |child_description| to a ListValue under key |child_key| in |
| +// |parent->representation|, creating said ListValue if needed. |
| +void InjectChildDescription(base::DictionaryValue child_description, |
| + DictionaryForestNode* parent, |
| + base::StringPiece child_key) { |
| + base::ListValue* children_weak = nullptr; |
| + base::DictionaryValue* parent_rep = &parent->representation; |
| + if (!parent_rep->GetList(child_key, &children_weak)) { |
| + auto children = base::MakeUnique<base::ListValue>(); |
| + children_weak = children.get(); |
| + parent_rep->Set(child_key, std::move(children)); |
| + } |
| + // TODO(crbug.com/646113) Get rid of the unique_ptr once ListValue::Append |
| + // takes the value directly. |
| + children_weak->Append( |
| + base::MakeUnique<base::Value>(std::move(child_description))); |
|
jdoerrie
2017/04/19 08:35:00
FYI, r465517 just landed, so you could get rid of
vabr (Chromium)
2017/04/19 11:03:17
Cool, thanks!
|
| +} |
| + |
| +} // namespace |
| + |
| +base::ListValue SerializeDictionaryForest( |
| + std::vector<DictionaryForestNode> forest, |
| + base::StringPiece child_key) { |
| + // To serialize |forest|, DictionaryValue descriptions of child nodes need to |
| + // be injected into DictionaryValue descriptions of their parents. That means |
| + // the forest needs to be processed in topological order, understanding the |
| + // direction of edges to go from child to parent. |
| + // |
| + // However, computing a topological order using Kahn's algorithm is easier |
| + // for forests where the edge direction goes from parents to children: the |
| + // condition for inserting a node into the resulting sorted list, "if node |
| + // has no other incoming edges", is trivial, because every node has at most |
| + // one parent and hence at most one incoming edge. |
| + // |
| + // Luckily, the reverse of a topological order of a graph is a topological |
| + // order of the same graph with edges reversed. So here first the easy |
| + // topological order for (parent -> child) edges is computed, and then |
| + // processed in reverse to serialize the forest. |
| + ChildrenRepresentation children(&forest); |
| + |
| + std::vector<DictionaryForestNode*> sorted; |
| + sorted.reserve(forest.size()); |
| + |
| + // Initialize the algorithm with nodes which come first in topological order. |
| + std::vector<DictionaryForestNode*> no_incoming_edges; |
| + no_incoming_edges.reserve(forest.size()); |
| + for (DictionaryForestNode& node : forest) { |
| + if (!node.parent) |
| + no_incoming_edges.push_back(&node); |
| + } |
| + |
| + // Iteratively add more nodes, respecting the topological order. |
| + while (!no_incoming_edges.empty()) { |
| + DictionaryForestNode* node = no_incoming_edges.back(); |
| + no_incoming_edges.pop_back(); |
| + sorted.push_back(node); |
| + for (DictionaryForestNode* child : children.For(node)) { |
| + // Once |node| is removed, all of its children have 0 incoming edges. |
| + no_incoming_edges.push_back(child); |
|
jdoerrie
2017/04/18 09:57:44
Another small suggestion:
Given that the children
vabr (Chromium)
2017/04/19 11:03:17
I like that, ChildrenRange was too much boilerplat
|
| + } |
| + } |
| + |
| + // Now let's build the representation: injecting DictionaryValues of children |
| + // into those of their parents, and adding the roots to the returned |
| + // ListValue. |
| + base::ListValue roots; |
| + for (auto node_it = sorted.rbegin(); node_it != sorted.rend(); ++node_it) { |
| + DictionaryForestNode* node = *node_it; |
| + DictionaryForestNode* parent = node->parent; |
| + if (parent) { |
| + InjectChildDescription(std::move(node->representation), parent, |
| + child_key); |
| + } else { |
| + // TODO(crbug.com/646113) Get rid of the unique_ptr once ListValue::Append |
| + // takes the value directly. |
| + roots.Append( |
| + base::MakeUnique<base::Value>(std::move(node->representation))); |
|
jdoerrie
2017/04/19 08:35:00
Same here, this could be:
roots.base::Value::GetL
vabr (Chromium)
2017/04/19 11:03:17
Done.
|
| + } |
| + } |
| + |
| + return roots; |
| +} |