Skip to content

Commit

Permalink
C++ Cleanup 9/N: YGAssert (facebook#1353)
Browse files Browse the repository at this point in the history
Summary:
X-link: facebook/react-native#39201

Pull Request resolved: facebook#1353

## This diff

This moves and renames `YGAssert`, and removes it from the public API, since external users should not need to call into internal Yoga assert functions, and the current API prevents us from making this a macro later to include the condition in the message.

## This stack

The organization of the C++ internals of Yoga are in need of attention.
1. Some of the C++ internals are namespaced, but others not.
2. Some of the namespaces include `detail`, but are meant to be used outside of the translation unit (FB Clang Tidy rules warn on any usage of these)
2. Most of the files are in a flat hierarchy, except for event tracing in its own folder
3. Some files and functions begin with YG, others don’t
4. Some functions are uppercase, others are not
5. Almost all of the interesting logic is in Yoga.cpp, and the file is too large to reason about
6. There are multiple grab bag files where folks put random functions they need in (Utils, BitUtils, Yoga-Internal.h)
7. There is no clear indication from file structure or type naming what is private vs not
8. Handles like `YGNodeRef` and `YGConfigRef` can be used to access internals just by importing headers

This stack does some much needed spring cleaning:
1. All non-public headers and C++ implementation details are in separate folders from the root level `yoga`. This will give us room to split up logic and add more files without too large a flat hierarchy
3. All private C++ internals are under the `facebook::yoga` namespace. Details namespaces are only ever used within the same header, as they are intended
4. Utils files are split
5. Most C++ internals drop the YG prefix
6. Most C++ internal function names are all lower camel case
7. We start to split up Yoga.cpp
8. Every header beginning with YG or at the top-level directory is public and C only, with the exception of Yoga-Internal.h which has non-public functions for bindings
9. It is not possible to use private APIs without static casting handles to internal classes

This will give us more leeway to continue splitting monolithic files, and consistent guidelines for style in new files as well.

These changes should not be breaking to any project using only public Yoga headers. This includes every usage of Yoga in fbsource except for RN Fabric which is currently tied to internals. This refactor should make that boundary clearer.

Reviewed By: rshest

Differential Revision: D48769809

fbshipit-source-id: 59bf61ebb3b8e629e4c90a9f040cd89cf8b192ec
  • Loading branch information
NickGerleman authored and facebook-github-bot committed Sep 4, 2023
1 parent 758849c commit 158c71f
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 88 deletions.
77 changes: 14 additions & 63 deletions yoga/Yoga.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
#include <cmath>
#include <cstring>
#include <memory>
#include <stdexcept>

#include <yoga/Yoga.h>
#include <yoga/Yoga-internal.h>

#include <yoga/algorithm/CollectFlexItemsRowValues.h>
#include <yoga/algorithm/FlexDirection.h>
#include <yoga/algorithm/ResolveValue.h>
#include <yoga/debug/Assert.h>
#include <yoga/debug/Log.h>
#include <yoga/debug/NodeToString.h>
#include <yoga/event/event.h>
Expand Down Expand Up @@ -190,8 +190,9 @@ int32_t gConfigInstanceCount = 0;

YOGA_EXPORT WIN_EXPORT YGNodeRef YGNodeNewWithConfig(const YGConfigRef config) {
auto* node = new yoga::Node{static_cast<yoga::Config*>(config)};
YGAssert(config != nullptr, "Tried to construct YGNode with null config");
YGAssertWithConfig(
yoga::assertFatal(
config != nullptr, "Tried to construct YGNode with null config");
yoga::assertFatalWithConfig(
config, node != nullptr, "Could not allocate memory for node");
Event::publish<Event::NodeAllocation>(node, {config});

Expand All @@ -210,7 +211,7 @@ YOGA_EXPORT YGNodeRef YGNodeNew(void) {
YOGA_EXPORT YGNodeRef YGNodeClone(YGNodeRef oldNodeRef) {
auto oldNode = static_cast<yoga::Node*>(oldNodeRef);
auto node = new yoga::Node(*oldNode);
YGAssertWithConfig(
yoga::assertFatalWithConfig(
oldNode->getConfig(),
node != nullptr,
"Could not allocate memory for node");
Expand Down Expand Up @@ -312,12 +313,12 @@ YOGA_EXPORT void YGNodeInsertChild(
auto owner = static_cast<yoga::Node*>(ownerRef);
auto child = static_cast<yoga::Node*>(childRef);

YGAssertWithNode(
yoga::assertFatalWithNode(
owner,
child->getOwner() == nullptr,
"Child already has a owner, it must be removed first.");

YGAssertWithNode(
yoga::assertFatalWithNode(
owner,
!owner->hasMeasureFunc(),
"Cannot add child: Nodes with measure functions cannot have children.");
Expand Down Expand Up @@ -456,7 +457,7 @@ YOGA_EXPORT YGNodeRef YGNodeGetParent(const YGNodeRef node) {
YOGA_EXPORT void YGNodeMarkDirty(const YGNodeRef nodeRef) {
auto node = static_cast<yoga::Node*>(nodeRef);

YGAssertWithNode(
yoga::assertFatalWithNode(
node,
node->hasMeasureFunc(),
"Only leaf nodes with custom measure functions "
Expand Down Expand Up @@ -943,7 +944,7 @@ YOGA_EXPORT YGValue YGNodeStyleGetMaxHeight(const YGNodeConstRef node) {
YOGA_EXPORT type YGNodeLayoutGet##name( \
const YGNodeRef nodeRef, const YGEdge edge) { \
auto node = static_cast<yoga::Node*>(nodeRef); \
YGAssertWithNode( \
yoga::assertFatalWithNode( \
node, \
edge <= YGEdgeEnd, \
"Cannot get layout properties of multi-edge shorthands"); \
Expand Down Expand Up @@ -1053,7 +1054,7 @@ static float YGBaseline(yoga::Node* node, void* layoutContext) {

Event::publish<Event::NodeBaselineEnd>(node);

YGAssertWithNode(
yoga::assertFatalWithNode(
node,
!yoga::isUndefined(baseline),
"Expect custom baseline function to not return NaN");
Expand Down Expand Up @@ -1666,7 +1667,7 @@ static void YGNodeWithMeasureFuncSetMeasuredDimensions(
LayoutData& layoutMarkerData,
void* const layoutContext,
const LayoutPassReason reason) {
YGAssertWithNode(
yoga::assertFatalWithNode(
node,
node->hasMeasureFunc(),
"Expected node to have custom measure function");
Expand Down Expand Up @@ -2725,13 +2726,13 @@ static void YGNodelayoutImpl(
const uint32_t depth,
const uint32_t generationCount,
const LayoutPassReason reason) {
YGAssertWithNode(
yoga::assertFatalWithNode(
node,
yoga::isUndefined(availableWidth) ? widthMeasureMode == YGMeasureModeUndefined
: true,
"availableWidth is indefinite so widthMeasureMode must be "
"YGMeasureModeUndefined");
YGAssertWithNode(
yoga::assertFatalWithNode(
node,
yoga::isUndefined(availableHeight) ? heightMeasureMode == YGMeasureModeUndefined
: true,
Expand Down Expand Up @@ -4079,7 +4080,7 @@ bool YGLayoutNodeInternal(
YOGA_EXPORT void YGConfigSetPointScaleFactor(
const YGConfigRef config,
const float pixelsInPoint) {
YGAssertWithConfig(
yoga::assertFatalWithConfig(
config,
pixelsInPoint >= 0.0f,
"Scale factor should not be less than zero");
Expand Down Expand Up @@ -4277,56 +4278,6 @@ YOGA_EXPORT void YGConfigSetLogger(const YGConfigRef config, YGLogger logger) {
}
}

static void fatalWithMessage(const char* message) {
#if defined(__cpp_exceptions)
throw std::logic_error(message);
#else
std::terminate();
#endif
}

void YGAssert(const bool condition, const char* message) {
if (!condition) {
yoga::log(
static_cast<yoga::Node*>(nullptr),
YGLogLevelFatal,
nullptr,
"%s\n",
message);
fatalWithMessage(message);
}
}

void YGAssertWithNode(
const YGNodeRef node,
const bool condition,
const char* message) {
if (!condition) {
yoga::log(
static_cast<yoga::Node*>(node),
YGLogLevelFatal,
nullptr,
"%s\n",
message);
fatalWithMessage(message);
}
}

void YGAssertWithConfig(
const YGConfigRef config,
const bool condition,
const char* message) {
if (!condition) {
yoga::log(
static_cast<yoga::Config*>(config),
YGLogLevelFatal,
nullptr,
"%s\n",
message);
fatalWithMessage(message);
}
}

YOGA_EXPORT void YGConfigSetExperimentalFeatureEnabled(
const YGConfigRef config,
const YGExperimentalFeature feature,
Expand Down
9 changes: 0 additions & 9 deletions yoga/Yoga.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,15 +303,6 @@ WIN_EXPORT float YGNodeLayoutGetBorder(YGNodeRef node, YGEdge edge);
WIN_EXPORT float YGNodeLayoutGetPadding(YGNodeRef node, YGEdge edge);

WIN_EXPORT void YGConfigSetLogger(YGConfigRef config, YGLogger logger);
WIN_EXPORT void YGAssert(bool condition, const char* message);
WIN_EXPORT void YGAssertWithNode(
YGNodeRef node,
bool condition,
const char* message);
WIN_EXPORT void YGAssertWithConfig(
YGConfigRef config,
bool condition,
const char* message);
// Set this to number of pixels in 1 point to round calculation results If you
// want to avoid rounding - set PointScaleFactor to 0
WIN_EXPORT void YGConfigSetPointScaleFactor(
Expand Down
14 changes: 4 additions & 10 deletions yoga/algorithm/FlexDirection.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

#include <yoga/Yoga.h>

#include <yoga/debug/Assert.h>

namespace facebook::yoga {

inline bool isRow(const YGFlexDirection flexDirection) {
Expand Down Expand Up @@ -55,11 +57,7 @@ inline YGEdge leadingEdge(const YGFlexDirection flexDirection) {
return YGEdgeRight;
}

YGAssert(false, "Invalid YGFlexDirection");

// Avoid "not all control paths return a value" warning until next diff adds
// assert with [[noreturn]]
return YGEdgeTop;
fatalWithMessage("Invalid YGFlexDirection");
}

inline YGEdge trailingEdge(const YGFlexDirection flexDirection) {
Expand All @@ -74,11 +72,7 @@ inline YGEdge trailingEdge(const YGFlexDirection flexDirection) {
return YGEdgeLeft;
}

YGAssert(false, "Invalid YGFlexDirection");

// Avoid "not all control paths return a value" warning until next diff adds
// assert with [[noreturn]]
return YGEdgeTop;
fatalWithMessage("Invalid YGFlexDirection");
}

} // namespace facebook::yoga
65 changes: 65 additions & 0 deletions yoga/debug/Assert.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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 <stdexcept>

#include <yoga/debug/Assert.h>
#include <yoga/debug/Log.h>

namespace facebook::yoga {

[[noreturn]] void fatalWithMessage(const char* message) {
#if defined(__cpp_exceptions)
throw std::logic_error(message);
#else
std::terminate();
#endif
}

void assertFatal(const bool condition, const char* message) {
if (!condition) {
yoga::log(
static_cast<yoga::Node*>(nullptr),
YGLogLevelFatal,
nullptr,
"%s\n",
message);
fatalWithMessage(message);
}
}

void assertFatalWithNode(
const YGNodeRef node,
const bool condition,
const char* message) {
if (!condition) {
yoga::log(
static_cast<yoga::Node*>(node),
YGLogLevelFatal,
nullptr,
"%s\n",
message);
fatalWithMessage(message);
}
}

void assertFatalWithConfig(
const YGConfigRef config,
const bool condition,
const char* message) {
if (!condition) {
yoga::log(
static_cast<yoga::Config*>(config),
YGLogLevelFatal,
nullptr,
"%s\n",
message);
fatalWithMessage(message);
}
}

} // namespace facebook::yoga
25 changes: 25 additions & 0 deletions yoga/debug/Assert.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.
*/

#pragma once

#include <yoga/Yoga.h>
#include <yoga/node/Node.h>
#include <yoga/config/Config.h>

namespace facebook::yoga {

[[noreturn]] void fatalWithMessage(const char* message);

void assertFatal(bool condition, const char* message);
void assertFatalWithNode(YGNodeRef node, bool condition, const char* message);
void assertFatalWithConfig(
YGConfigRef config,
bool condition,
const char* message);

} // namespace facebook::yoga
15 changes: 9 additions & 6 deletions yoga/node/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@

#include <yoga/algorithm/FlexDirection.h>
#include <yoga/algorithm/ResolveValue.h>
#include <yoga/debug/Assert.h>
#include <yoga/node/Node.h>
#include <yoga/numeric/Comparison.h>

namespace facebook::yoga {

Node::Node(yoga::Config* config) : config_{config} {
YGAssert(config != nullptr, "Attempting to construct Node with null config");
yoga::assertFatal(
config != nullptr, "Attempting to construct Node with null config");

flags_.hasNewLayout = true;
if (config->useWebDefaults()) {
Expand Down Expand Up @@ -234,7 +236,7 @@ void Node::setMeasureFunc(decltype(Node::measure_) measureFunc) {
// places in Litho
setNodeType(YGNodeTypeDefault);
} else {
YGAssertWithNode(
yoga::assertFatalWithNode(
this,
children_.size() == 0,
"Cannot set measure function: Nodes with measure functions cannot have "
Expand Down Expand Up @@ -274,8 +276,9 @@ void Node::insertChild(Node* child, uint32_t index) {
}

void Node::setConfig(yoga::Config* config) {
YGAssert(config != nullptr, "Attempting to set a null config on a Node");
YGAssertWithConfig(
yoga::assertFatal(
config != nullptr, "Attempting to set a null config on a Node");
yoga::assertFatalWithConfig(
config,
config->useWebDefaults() == config_->useWebDefaults(),
"UseWebDefaults may not be changed after constructing a Node");
Expand Down Expand Up @@ -592,11 +595,11 @@ FloatOptional Node::getTrailingPaddingAndBorder(
}

void Node::reset() {
YGAssertWithNode(
yoga::assertFatalWithNode(
this,
children_.size() == 0,
"Cannot reset a node which still has children attached");
YGAssertWithNode(
yoga::assertFatalWithNode(
this, owner_ == nullptr, "Cannot reset a node still attached to a owner");

*this = Node{getConfig()};
Expand Down

0 comments on commit 158c71f

Please sign in to comment.