Files
yoga/capture/CaptureTree.cpp
Joe Vilches e2ed3f031d 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
2024-02-13 17:22:08 -08:00

95 lines
2.7 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <fstream>
#include <vector>
#include <capture/CaptureTree.h>
#include <capture/NodeToString.h>
#include <nlohmann/json.hpp>
namespace facebook::yoga {
using namespace nlohmann;
static void captureTree(
std::string_view serializedTree,
const std::filesystem::path& path) {
std::ofstream file(path);
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(
j,
node,
PrintOptions::Style | PrintOptions::Children | PrintOptions::Config |
PrintOptions::Node);
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