Support for (de)serializing measure funcs
Summary: In addition to all the state that gets set on the node that is easy to serialize - like floats, enums, bools, etc - we also need to serialize measure functions. This is because these functions take a nontrivial amount of time up during layout and we should capture that. Also, they are important to the ability to truly replay layout as it was captured as the results of the measure functions determine many of the steps the layout algorithm takes. Capturing this is rather tricky however, but I think I found a solution that is relatively simple and non-error prone. Essentially, since we are capturing the entire tree and virtually every input that goes into the flexbox algorithm, we *should* be able to replay layout exactly as it was captured. This means that the order in which measure functions are called *should* be the same. If this is the case, then all we need to do to capture the measure functions is store their input, output, and duration in a big array. During deserialization we just keep track of an index and use that to determine which measure function we should call. That is the premise behind what happens in this diff. In theory the algorithm could change and the capture would be wrong but it is easy enough to recapture again. Additionally we need to dirty the tree so that we get rid of caching which might omit some measure func calls In order to capture you need to insert a method exposed by CaptureTree.h into the client measure func, which is kind of annoying but not that bad. In future diffs I will put a macro in place to make this even easier. I also add our first capture! Which is of a large react native desktop app Reviewed By: NickGerleman Differential Revision: D53581121 fbshipit-source-id: 876a230208d67f0ecf76844a4f1b80048353aae2
This commit is contained in:
committed by
Facebook GitHub Bot
parent
cc66362a28
commit
e2ed3f031d
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
#include <capture/CaptureTree.h>
|
||||
#include <capture/NodeToString.h>
|
||||
@@ -22,12 +23,40 @@ static void captureTree(
|
||||
file << serializedTree;
|
||||
}
|
||||
|
||||
static std::vector<SerializedMeasureFunc>& currentSerializedMeasureFuncVec() {
|
||||
static thread_local std::vector<SerializedMeasureFunc>
|
||||
currentSerializedMeasureFuncVec;
|
||||
return currentSerializedMeasureFuncVec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Capturing a tree often means that we capturing multiple serial layouts over
|
||||
* the course of the capture. Because of this, we need to make sure that we do
|
||||
* a full layout pass with no caching. If we do not do this there is a chance
|
||||
* we do not capture measure functions that were called and cached in previous
|
||||
* layouts. Missing these captures would lead to inaccurate benchmarking where
|
||||
* we do not have cached state.
|
||||
*
|
||||
* TODO: Dirty entire tree not just measure function nodes
|
||||
*/
|
||||
static void dirtyTree(YGNodeRef node) {
|
||||
if (YGNodeHasMeasureFunc(node)) {
|
||||
YGNodeMarkDirty(node);
|
||||
}
|
||||
|
||||
const size_t childCount = YGNodeGetChildCount(node);
|
||||
for (size_t i = 0; i < childCount; i++) {
|
||||
dirtyTree(YGNodeGetChild(node, i));
|
||||
}
|
||||
}
|
||||
|
||||
void YGNodeCalculateLayoutWithCapture(
|
||||
YGNodeRef node,
|
||||
float availableWidth,
|
||||
float availableHeight,
|
||||
YGDirection ownerDirection,
|
||||
const std::filesystem::path& path) {
|
||||
dirtyTree(node);
|
||||
json j;
|
||||
serializeLayoutInputs(j, availableWidth, availableHeight, ownerDirection);
|
||||
serializeTree(
|
||||
@@ -35,8 +64,31 @@ void YGNodeCalculateLayoutWithCapture(
|
||||
node,
|
||||
PrintOptions::Style | PrintOptions::Children | PrintOptions::Config |
|
||||
PrintOptions::Node);
|
||||
captureTree(j.dump(2), path);
|
||||
|
||||
YGNodeCalculateLayout(node, availableWidth, availableHeight, ownerDirection);
|
||||
|
||||
serializeMeasureFuncResults(j, currentSerializedMeasureFuncVec());
|
||||
// TODO: It is possible to have a measure function call layout again if, e.g.,
|
||||
// views are nested in text. Need to be able to resolve this special case.
|
||||
currentSerializedMeasureFuncVec().clear();
|
||||
captureTree(j.dump(2), path);
|
||||
}
|
||||
|
||||
void captureMeasureFunc(
|
||||
float width,
|
||||
YGMeasureMode widthMode,
|
||||
float height,
|
||||
YGMeasureMode heightMode,
|
||||
YGSize output,
|
||||
std::chrono::steady_clock::duration durationNs) {
|
||||
currentSerializedMeasureFuncVec().push_back(SerializedMeasureFunc{
|
||||
width,
|
||||
widthMode,
|
||||
height,
|
||||
heightMode,
|
||||
output.width,
|
||||
output.height,
|
||||
durationNs.count()});
|
||||
}
|
||||
|
||||
} // namespace facebook::yoga
|
||||
|
@@ -13,6 +13,16 @@
|
||||
|
||||
namespace facebook::yoga {
|
||||
|
||||
struct SerializedMeasureFunc {
|
||||
float inputWidth{0.0f};
|
||||
YGMeasureMode widthMode{YGMeasureModeUndefined};
|
||||
float inputHeight{0.0};
|
||||
YGMeasureMode heightMode{YGMeasureModeUndefined};
|
||||
float outputWidth{0.0f};
|
||||
float outputHeight{0.0f};
|
||||
std::chrono::steady_clock::duration::rep durationNs;
|
||||
};
|
||||
|
||||
void YGNodeCalculateLayoutWithCapture(
|
||||
YGNodeRef node,
|
||||
float availableWidth,
|
||||
@@ -20,4 +30,12 @@ void YGNodeCalculateLayoutWithCapture(
|
||||
YGDirection ownerDirection,
|
||||
const std::filesystem::path& path);
|
||||
|
||||
void captureMeasureFunc(
|
||||
float width,
|
||||
YGMeasureMode widthMode,
|
||||
float height,
|
||||
YGMeasureMode heightMode,
|
||||
YGSize output,
|
||||
std::chrono::steady_clock::duration durationNs);
|
||||
|
||||
} // namespace facebook::yoga
|
||||
|
@@ -114,13 +114,13 @@ static void appendEdges(
|
||||
(*Field)(defaultNode, YGEdgeHorizontal));
|
||||
}
|
||||
|
||||
YGValue borderFloatToYGValue(YGNodeRef node, YGEdge edge) {
|
||||
static YGValue borderFloatToYGValue(YGNodeRef node, YGEdge edge) {
|
||||
float val = YGNodeStyleGetBorder(node, edge);
|
||||
YGUnit unit = YGFloatIsUndefined(val) ? YGUnitUndefined : YGUnitPoint;
|
||||
return YGValue{val, unit};
|
||||
}
|
||||
|
||||
void serializeTree(json& j, YGNodeRef node, PrintOptions options) {
|
||||
static void serializeTreeImpl(json& j, YGNodeRef node, PrintOptions options) {
|
||||
if ((options & PrintOptions::Layout) == PrintOptions::Layout) {
|
||||
j["layout"]["width"] = YGNodeStyleGetWidth(node).value;
|
||||
j["layout"]["height"] = YGNodeStyleGetHeight(node).value;
|
||||
@@ -302,11 +302,15 @@ void serializeTree(json& j, YGNodeRef node, PrintOptions options) {
|
||||
childCount > 0) {
|
||||
for (size_t i = 0; i < childCount; i++) {
|
||||
j["children"].push_back({});
|
||||
serializeTree(j["children"][i], YGNodeGetChild(node, i), options);
|
||||
serializeTreeImpl(j["children"][i], YGNodeGetChild(node, i), options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void serializeTree(json& j, YGNodeRef node, PrintOptions options) {
|
||||
serializeTreeImpl(j["tree"], node, options);
|
||||
}
|
||||
|
||||
void serializeLayoutInputs(
|
||||
json& j,
|
||||
float availableWidth,
|
||||
@@ -319,4 +323,19 @@ void serializeLayoutInputs(
|
||||
};
|
||||
}
|
||||
|
||||
void serializeMeasureFuncResults(
|
||||
json& j,
|
||||
std::vector<SerializedMeasureFunc>& measureFuncs) {
|
||||
for (auto measureFunc : measureFuncs) {
|
||||
j["measure-funcs"].push_back(
|
||||
{{"width", measureFunc.inputWidth},
|
||||
{"width-mode", YGMeasureModeToString(measureFunc.widthMode)},
|
||||
{"height", measureFunc.inputHeight},
|
||||
{"height-mode", YGMeasureModeToString(measureFunc.heightMode)},
|
||||
{"output-width", measureFunc.outputWidth},
|
||||
{"output-height", measureFunc.outputHeight},
|
||||
{"duration-ns", measureFunc.durationNs}});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace facebook::yoga
|
||||
|
@@ -7,8 +7,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include <capture/CaptureTree.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <yoga/Yoga.h>
|
||||
|
||||
@@ -31,4 +33,8 @@ void serializeLayoutInputs(
|
||||
float availableHeight,
|
||||
YGDirection ownerDirection);
|
||||
|
||||
void serializeMeasureFuncResults(
|
||||
nlohmann::json& j,
|
||||
std::vector<SerializedMeasureFunc>& measureFuncs);
|
||||
|
||||
} // namespace facebook::yoga
|
||||
|
Reference in New Issue
Block a user