2024-02-05 11:48:07 -08:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
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
|
|
|
#include <chrono>
|
2024-02-06 11:16:21 -08:00
|
|
|
#include <filesystem>
|
2024-02-05 11:48:07 -08:00
|
|
|
#include <fstream>
|
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
|
|
|
#include <iostream>
|
|
|
|
#include <thread>
|
2024-02-05 11:48:07 -08:00
|
|
|
|
|
|
|
#include <benchmark/Benchmark.h>
|
2024-02-09 16:44:32 -08:00
|
|
|
#include <benchmark/TreeDeserialization.h>
|
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
|
|
|
#include <capture/CaptureTree.h>
|
2024-02-05 11:48:07 -08:00
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
|
|
|
|
namespace facebook::yoga {
|
|
|
|
|
|
|
|
using namespace nlohmann;
|
2024-02-05 11:48:07 -08:00
|
|
|
using namespace std::chrono;
|
|
|
|
|
2024-03-19 02:52:40 -07:00
|
|
|
constexpr uint32_t kNumRepetitions = 100;
|
2024-02-05 11:48:07 -08:00
|
|
|
using SteadyClockDurations =
|
2024-03-19 02:52:40 -07:00
|
|
|
std::array<steady_clock::duration, kNumRepetitions>;
|
2024-02-05 11:48:07 -08:00
|
|
|
|
2024-02-13 17:22:08 -08:00
|
|
|
static bool inputsMatch(
|
|
|
|
float actualWidth,
|
|
|
|
float expectedWidth,
|
|
|
|
float actualHeight,
|
|
|
|
float expectedHeight,
|
|
|
|
YGMeasureMode actualWidthMode,
|
|
|
|
YGMeasureMode expectedWidthMode,
|
|
|
|
YGMeasureMode actualHeightMode,
|
|
|
|
YGMeasureMode expectedHeightMode) {
|
|
|
|
bool widthsAreUndefined =
|
|
|
|
YGFloatIsUndefined(actualWidth) && YGFloatIsUndefined(expectedWidth);
|
|
|
|
bool widthsAreEqual = widthsAreUndefined || actualWidth == expectedWidth;
|
|
|
|
bool heightsAreUndefined =
|
|
|
|
YGFloatIsUndefined(actualHeight) && YGFloatIsUndefined(expectedHeight);
|
|
|
|
bool heightsAreEqual = heightsAreUndefined || actualHeight == expectedHeight;
|
|
|
|
|
|
|
|
return widthsAreEqual && heightsAreEqual &&
|
|
|
|
actualWidthMode == expectedWidthMode &&
|
|
|
|
actualHeightMode == expectedHeightMode;
|
|
|
|
}
|
|
|
|
|
2024-02-21 18:02:58 -08:00
|
|
|
YGSize defaultMeasureFunctionResult() {
|
|
|
|
std::cout << "Trying to measure a node that wasn't serialized" << std::endl;
|
|
|
|
return {10.0, 10.0};
|
|
|
|
}
|
|
|
|
|
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
|
|
|
YGSize mockMeasureFunc(
|
|
|
|
YGNodeConstRef node,
|
|
|
|
float availableWidth,
|
|
|
|
YGMeasureMode widthMode,
|
|
|
|
float availableHeight,
|
|
|
|
YGMeasureMode heightMode) {
|
|
|
|
(void)node;
|
2024-02-21 18:02:58 -08:00
|
|
|
auto fnsPtr = static_cast<SerializedMeasureFuncMap*>(YGNodeGetContext(node));
|
2024-02-13 17:22:08 -08:00
|
|
|
|
2024-02-21 18:02:58 -08:00
|
|
|
if (fnsPtr == nullptr) {
|
|
|
|
return defaultMeasureFunctionResult();
|
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
|
|
|
}
|
|
|
|
|
2024-02-21 18:02:58 -08:00
|
|
|
auto fnsIt = fnsPtr->find(node);
|
|
|
|
if (fnsIt == fnsPtr->end()) {
|
|
|
|
return defaultMeasureFunctionResult();
|
2024-02-13 17:22:08 -08:00
|
|
|
}
|
|
|
|
|
2024-02-21 18:02:58 -08:00
|
|
|
for (auto measureFunc : fnsIt->second) {
|
|
|
|
if (inputsMatch(
|
|
|
|
availableWidth,
|
|
|
|
measureFunc.inputWidth,
|
|
|
|
availableHeight,
|
|
|
|
measureFunc.inputHeight,
|
|
|
|
widthMode,
|
|
|
|
measureFunc.widthMode,
|
|
|
|
heightMode,
|
|
|
|
measureFunc.heightMode)) {
|
|
|
|
std::this_thread::sleep_for(
|
|
|
|
std::chrono::nanoseconds(measureFunc.durationNs));
|
|
|
|
return {measureFunc.outputWidth, measureFunc.outputHeight};
|
|
|
|
}
|
|
|
|
}
|
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
|
|
|
|
2024-02-21 18:02:58 -08:00
|
|
|
return defaultMeasureFunctionResult();
|
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
|
|
|
}
|
|
|
|
|
2024-02-09 16:44:32 -08:00
|
|
|
std::shared_ptr<const YGConfig> buildConfigFromJson(const json& j) {
|
2024-02-09 16:44:32 -08:00
|
|
|
json jsonConfig = j["config"];
|
|
|
|
std::shared_ptr<YGConfig> config(YGConfigNew(), YGConfigFree);
|
|
|
|
for (json::iterator it = jsonConfig.begin(); it != jsonConfig.end(); it++) {
|
|
|
|
if (it.key() == "use-web-defaults") {
|
|
|
|
YGConfigSetUseWebDefaults(config.get(), it.value());
|
|
|
|
} else if (it.key() == "point-scale-factor") {
|
|
|
|
YGConfigSetPointScaleFactor(config.get(), it.value());
|
|
|
|
} else if (it.key() == "errata") {
|
|
|
|
YGConfigSetErrata(config.get(), errataFromString(it.value()));
|
|
|
|
} else if (it.key() == "experimental-features") {
|
|
|
|
// Experimental features is serialized into an array where the values
|
|
|
|
// present indicate that that feature is enabled
|
|
|
|
for (json::iterator efIt = it.value().begin(); efIt != it.value().end();
|
|
|
|
efIt++) {
|
|
|
|
YGConfigSetExperimentalFeatureEnabled(
|
|
|
|
config.get(), experimentalFeatureFromString(efIt.value()), true);
|
|
|
|
}
|
|
|
|
}
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
|
2024-02-09 16:44:32 -08:00
|
|
|
return config;
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string edgeStringFromPropertyName(
|
2024-03-04 02:28:02 -08:00
|
|
|
const std::string& key,
|
|
|
|
const std::string& propertyName) {
|
2024-02-05 11:48:07 -08:00
|
|
|
return key.substr(propertyName.length() + 1);
|
|
|
|
}
|
|
|
|
|
2024-02-09 16:44:32 -08:00
|
|
|
void setStylesFromJson(const json& j, YGNodeRef node) {
|
2024-02-05 11:48:07 -08:00
|
|
|
json style = j["style"];
|
|
|
|
for (const auto& [key, value] : style.items()) {
|
|
|
|
if (key == "flex-direction") {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetFlexDirection(node, flexDirectionFromString(value));
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (key == "justify-content") {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetJustifyContent(node, justifyContentFromString(value));
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (key == "align-items") {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetAlignItems(node, alignFromString(value));
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (key == "align-content") {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetAlignContent(node, alignFromString(value));
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (key == "align-self") {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetAlignSelf(node, alignFromString(value));
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (key == "flex-wrap") {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetFlexWrap(node, wrapFromString(value));
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (key == "overflow") {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetOverflow(node, overflowFromString(value));
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (key == "display") {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetDisplay(node, displayFromString(value));
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (key == "position-type") {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetPositionType(node, positionTypeFromString(value));
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (key == "flex-grow") {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetFlexGrow(node, value);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (key == "flex-shrink") {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetFlexShrink(node, value);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (key == "flex") {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetFlex(node, value);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (key == "flex-basis") {
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitAuto) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetFlexBasisAuto(node);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetFlexBasis(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPercent) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetFlexBasisPercent(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key.starts_with("position")) {
|
|
|
|
YGEdge edge = edgeFromString(edgeStringFromPropertyName(key, "position"));
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetPosition(node, edge, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPercent) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetPositionPercent(node, edge, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key.starts_with("padding")) {
|
|
|
|
YGEdge edge = edgeFromString(edgeStringFromPropertyName(key, "padding"));
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetPadding(node, edge, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPercent) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetPaddingPercent(node, edge, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key.starts_with("border")) {
|
|
|
|
YGEdge edge = edgeFromString(edgeStringFromPropertyName(key, "border"));
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetBorder(node, edge, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key.starts_with("margin")) {
|
|
|
|
YGEdge edge = edgeFromString(edgeStringFromPropertyName(key, "margin"));
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetMargin(node, edge, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPercent) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetMarginPercent(node, edge, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitAuto) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetMarginAuto(node, edge);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key == "gap") {
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetGap(node, YGGutterAll, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key == "column-gap") {
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetGap(node, YGGutterColumn, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key == "row-gap") {
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetGap(node, YGGutterRow, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key == "height") {
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitAuto) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetHeightAuto(node);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetHeight(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPercent) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetHeightPercent(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key == "width") {
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitAuto) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetWidthAuto(node);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetWidth(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPercent) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetWidthPercent(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key == "min-height") {
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetMinHeight(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPercent) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetMinHeightPercent(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key == "min-width") {
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetMinWidth(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPercent) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetMinWidthPercent(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key == "max-height") {
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetMaxHeight(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPercent) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetMaxHeightPercent(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
} else if (key == "max-width") {
|
|
|
|
YGUnit unit = unitFromJson(value);
|
|
|
|
if (unit == YGUnitPoint) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetMaxWidth(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
} else if (unit == YGUnitPercent) {
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeStyleSetMaxWidthPercent(node, value["value"]);
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-02-09 16:44:32 -08:00
|
|
|
}
|
|
|
|
|
2024-02-09 16:44:32 -08:00
|
|
|
std::shared_ptr<YGNode> buildNodeFromJson(
|
|
|
|
const json& j,
|
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
|
|
|
std::shared_ptr<const YGConfig> config,
|
2024-02-21 18:02:58 -08:00
|
|
|
std::shared_ptr<SerializedMeasureFuncMap> fns) {
|
2024-02-09 16:44:32 -08:00
|
|
|
std::shared_ptr<YGNode> node(YGNodeNewWithConfig(config.get()), YGNodeFree);
|
2024-02-09 16:44:32 -08:00
|
|
|
|
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
|
|
|
if (!j.contains("node") || j["node"].is_null()) {
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
json nodeState = j["node"];
|
2024-02-09 16:44:32 -08:00
|
|
|
for (json::iterator it = nodeState.begin(); it != nodeState.end(); it++) {
|
|
|
|
if (it.key() == "always-forms-containing-block") {
|
|
|
|
YGNodeSetAlwaysFormsContainingBlock(node.get(), it.value());
|
2024-02-21 18:02:58 -08:00
|
|
|
} else if (it.key() == "measure-funcs") {
|
|
|
|
std::vector<SerializedMeasureFunc> vec{};
|
|
|
|
for (auto measureFuncJson : it.value()) {
|
|
|
|
vec.push_back(serializedMeasureFuncFromJson(measureFuncJson));
|
|
|
|
}
|
|
|
|
fns->insert(std::make_pair(node.get(), vec));
|
|
|
|
YGNodeSetContext(node.get(), it.value().is_null() ? nullptr : fns.get());
|
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
|
|
|
YGNodeSetMeasureFunc(node.get(), mockMeasureFunc);
|
2024-02-09 16:44:32 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
std::shared_ptr<YogaNodeAndConfig> buildTreeFromJson(
|
|
|
|
const json& j,
|
2024-02-21 18:02:58 -08:00
|
|
|
std::shared_ptr<SerializedMeasureFuncMap> fns,
|
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
|
|
|
std::shared_ptr<YogaNodeAndConfig> parent,
|
|
|
|
size_t index) {
|
|
|
|
auto config = buildConfigFromJson(j);
|
|
|
|
auto node = buildNodeFromJson(j, config, fns);
|
|
|
|
auto wrapper = std::make_shared<YogaNodeAndConfig>(
|
|
|
|
node, config, std::vector<std::shared_ptr<YogaNodeAndConfig>>{});
|
|
|
|
|
2024-02-09 16:44:32 -08:00
|
|
|
if (parent != nullptr) {
|
|
|
|
YGNodeInsertChild(parent->node_.get(), node.get(), index);
|
|
|
|
parent->children_.push_back(wrapper);
|
|
|
|
}
|
|
|
|
|
2024-02-09 16:44:32 -08:00
|
|
|
setStylesFromJson(j, node.get());
|
2024-02-05 11:48:07 -08:00
|
|
|
|
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
|
|
|
if (j.contains("children")) {
|
|
|
|
json children = j["children"];
|
|
|
|
size_t childIndex = 0;
|
2024-03-04 02:28:02 -08:00
|
|
|
for (const json& child : children) {
|
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
|
|
|
buildTreeFromJson(child, fns, wrapper, childIndex);
|
|
|
|
childIndex++;
|
|
|
|
}
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
|
2024-02-09 16:44:32 -08:00
|
|
|
return wrapper;
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
|
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
|
|
|
BenchmarkResult generateBenchmark(json& capture) {
|
2024-02-21 18:02:58 -08:00
|
|
|
auto fns = std::make_shared<SerializedMeasureFuncMap>();
|
2024-02-05 11:48:07 -08:00
|
|
|
|
2024-02-05 11:48:07 -08:00
|
|
|
auto treeCreationBegin = steady_clock::now();
|
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
|
|
|
std::shared_ptr<YogaNodeAndConfig> root =
|
|
|
|
buildTreeFromJson(capture["tree"], fns, nullptr, 0 /*index*/);
|
2024-02-05 11:48:07 -08:00
|
|
|
auto treeCreationEnd = steady_clock::now();
|
2024-02-05 11:48:07 -08:00
|
|
|
|
2024-02-09 16:44:32 -08:00
|
|
|
json layoutInputs = capture["layout-inputs"];
|
|
|
|
float availableWidth = layoutInputs["available-width"];
|
|
|
|
float availableHeight = layoutInputs["available-height"];
|
|
|
|
YGDirection direction = directionFromString(layoutInputs["owner-direction"]);
|
|
|
|
|
2024-02-05 11:48:07 -08:00
|
|
|
auto layoutBegin = steady_clock::now();
|
2024-02-09 16:44:32 -08:00
|
|
|
YGNodeCalculateLayout(
|
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
|
|
|
root->node_.get(), availableWidth, availableHeight, direction);
|
2024-02-05 11:48:07 -08:00
|
|
|
auto layoutEnd = steady_clock::now();
|
2024-02-05 11:48:07 -08:00
|
|
|
|
2024-02-05 11:48:07 -08:00
|
|
|
return BenchmarkResult{
|
|
|
|
treeCreationEnd - treeCreationBegin, layoutEnd - layoutBegin};
|
|
|
|
}
|
|
|
|
|
|
|
|
static void printBenchmarkResult(
|
2024-03-04 02:28:02 -08:00
|
|
|
const std::string& name,
|
2024-02-05 11:48:07 -08:00
|
|
|
SteadyClockDurations& durations) {
|
2024-03-19 02:52:40 -07:00
|
|
|
std::array<double, kNumRepetitions> timesInMs{};
|
2024-02-05 11:48:07 -08:00
|
|
|
double mean = 0;
|
2024-03-19 02:52:40 -07:00
|
|
|
for (uint32_t i = 0; i < kNumRepetitions; i++) {
|
2024-02-05 11:48:07 -08:00
|
|
|
auto ms = duration<double, std::milli>(durations[i]).count();
|
|
|
|
timesInMs[i] = ms;
|
|
|
|
mean += ms;
|
|
|
|
}
|
2024-03-19 02:52:40 -07:00
|
|
|
mean /= kNumRepetitions;
|
2024-02-05 11:48:07 -08:00
|
|
|
|
|
|
|
std::sort(timesInMs.begin(), timesInMs.end());
|
2024-03-19 02:52:40 -07:00
|
|
|
double median = timesInMs[kNumRepetitions / 2];
|
2024-02-05 11:48:07 -08:00
|
|
|
|
|
|
|
double variance = 0;
|
2024-03-19 02:52:40 -07:00
|
|
|
for (uint32_t i = 0; i < kNumRepetitions; i++) {
|
2024-02-05 11:48:07 -08:00
|
|
|
variance += std::pow(timesInMs[i] - mean, 2);
|
|
|
|
}
|
2024-03-19 02:52:40 -07:00
|
|
|
variance /= kNumRepetitions;
|
2024-02-05 11:48:07 -08:00
|
|
|
double stddev = std::sqrt(variance);
|
|
|
|
|
|
|
|
printf("%s: median: %lf ms, stddev: %lf ms\n", name.c_str(), median, stddev);
|
|
|
|
}
|
|
|
|
|
|
|
|
void benchmark(std::filesystem::path& capturesDir) {
|
|
|
|
for (auto& capture : std::filesystem::directory_iterator(capturesDir)) {
|
|
|
|
if (capture.is_directory() || capture.path().extension() != ".json") {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
SteadyClockDurations treeCreationDurations;
|
|
|
|
SteadyClockDurations layoutDurations;
|
|
|
|
SteadyClockDurations totalDurations;
|
|
|
|
|
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
|
|
|
std::ifstream captureFile(capture.path());
|
|
|
|
json j = json::parse(captureFile);
|
2024-02-13 17:22:08 -08:00
|
|
|
std::string captureName = capture.path().stem().string();
|
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
|
|
|
|
2024-02-13 17:22:08 -08:00
|
|
|
std::cout << "Starting benchmark for " << captureName << std::endl;
|
2024-03-19 02:52:40 -07:00
|
|
|
for (uint32_t i = 0; i < kNumRepetitions; i++) {
|
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
|
|
|
BenchmarkResult result = generateBenchmark(j);
|
2024-02-05 11:48:07 -08:00
|
|
|
treeCreationDurations[i] = result.treeCreationDuration;
|
|
|
|
layoutDurations[i] = result.layoutDuration;
|
|
|
|
totalDurations[i] = result.treeCreationDuration + result.layoutDuration;
|
|
|
|
}
|
|
|
|
|
|
|
|
printBenchmarkResult(captureName + " tree creation", treeCreationDurations);
|
|
|
|
printBenchmarkResult(captureName + " layout", layoutDurations);
|
|
|
|
printBenchmarkResult(captureName + " total", totalDurations);
|
2024-02-13 17:22:08 -08:00
|
|
|
|
|
|
|
std::cout << std::endl;
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
2024-02-05 11:48:07 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace facebook::yoga
|
|
|
|
|
2024-02-05 11:48:07 -08:00
|
|
|
int main(int argc, char* argv[]) {
|
|
|
|
if (argc == 2) {
|
|
|
|
std::filesystem::path capturesDir = argv[argc - 1];
|
|
|
|
facebook::yoga::benchmark(capturesDir);
|
|
|
|
} else {
|
|
|
|
throw std::invalid_argument("Expecting a path as an argument");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2024-02-05 11:48:07 -08:00
|
|
|
return 0;
|
|
|
|
}
|