diff --git a/lib/yoga/src/main/cpp/yoga/YGNodeStyle.cpp b/lib/yoga/src/main/cpp/yoga/YGNodeStyle.cpp index ac24f5f1494..d8454053dee 100644 --- a/lib/yoga/src/main/cpp/yoga/YGNodeStyle.cpp +++ b/lib/yoga/src/main/cpp/yoga/YGNodeStyle.cpp @@ -34,15 +34,13 @@ void updateStyle(YGNodeRef node, IdxT idx, ValueT value) { } // namespace -void YGNodeCopyStyle( - const YGNodeRef dstNodeRef, - const YGNodeConstRef srcNodeRef) { - auto dstNode = resolveRef(dstNodeRef); - auto srcNode = resolveRef(srcNodeRef); - - if (!(dstNode->style() == srcNode->style())) { - dstNode->setStyle(srcNode->style()); - dstNode->markDirtyAndPropagate(); +void YGNodeCopyStyle(YGNodeRef dstNode, YGNodeConstRef srcNode) { + auto dst = resolveRef(dstNode); + auto src = resolveRef(srcNode); + + if (dst->style() != src->style()) { + dst->setStyle(src->style()); + dst->markDirtyAndPropagate(); } } diff --git a/lib/yoga/src/main/cpp/yoga/node/Node.h b/lib/yoga/src/main/cpp/yoga/node/Node.h index d2fdb20aac4..1eea46f8c1c 100644 --- a/lib/yoga/src/main/cpp/yoga/node/Node.h +++ b/lib/yoga/src/main/cpp/yoga/node/Node.h @@ -274,13 +274,13 @@ class YG_EXPORT Node : public ::YGNode { YGMeasureFunc measureFunc_ = nullptr; YGBaselineFunc baselineFunc_ = nullptr; YGDirtiedFunc dirtiedFunc_ = nullptr; - Style style_ = {}; - LayoutResults layout_ = {}; + Style style_; + LayoutResults layout_; size_t lineIndex_ = 0; Node* owner_ = nullptr; - std::vector children_ = {}; + std::vector children_; const Config* config_; - std::array resolvedDimensions_ = { + std::array resolvedDimensions_{ {value::undefined(), value::undefined()}}; }; diff --git a/lib/yoga/src/main/cpp/yoga/style/CompactValue.h b/lib/yoga/src/main/cpp/yoga/style/CompactValue.h deleted file mode 100644 index e80f2a9f22f..00000000000 --- a/lib/yoga/src/main/cpp/yoga/style/CompactValue.h +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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 -#include -#include -#include - -#include -#include - -#include -#include - -static_assert( - std::numeric_limits::is_iec559, - "facebook::yoga::detail::CompactValue only works with IEEE754 floats"); - -#ifdef YOGA_COMPACT_VALUE_TEST -#define VISIBLE_FOR_TESTING public: -#else -#define VISIBLE_FOR_TESTING private: -#endif - -namespace facebook::yoga { - -// This class stores YGValue in 32 bits. -// - The value does not matter for Undefined and Auto. NaNs are used for their -// representation. -// - To differentiate between Point and Percent, one exponent bit is used. -// Supported the range [0x40, 0xbf] (0xbf is inclusive for point, but -// exclusive for percent). -// - Value ranges: -// points: 1.08420217e-19f to 36893485948395847680 -// 0x00000000 0x3fffffff -// percent: 1.08420217e-19f to 18446742974197923840 -// 0x40000000 0x7f7fffff -// - Zero is supported, negative zero is not -// - values outside of the representable range are clamped -class CompactValue { - friend constexpr bool operator==(CompactValue, CompactValue) noexcept; - - public: - static constexpr auto LOWER_BOUND = 1.08420217e-19f; - static constexpr auto UPPER_BOUND_POINT = 36893485948395847680.0f; - static constexpr auto UPPER_BOUND_PERCENT = 18446742974197923840.0f; - - static constexpr CompactValue ofUndefined() noexcept { - return CompactValue{}; - } - - static constexpr CompactValue ofAuto() noexcept { - return CompactValue{AUTO_BITS}; - } - - constexpr CompactValue() noexcept = default; - - explicit constexpr CompactValue(const StyleLength& x) noexcept { - switch (x.unit()) { - case Unit::Undefined: - *this = ofUndefined(); - break; - case Unit::Auto: - *this = ofAuto(); - break; - case Unit::Point: - *this = of(x.value().unwrap()); - break; - case Unit::Percent: - *this = of(x.value().unwrap()); - break; - } - } - - explicit operator StyleLength() const noexcept { - if (repr_ == 0x7FC00000) { - return value::undefined(); - } - - switch (repr_) { - case AUTO_BITS: - return value::ofAuto(); - case ZERO_BITS_POINT: - return value::points(0); - case ZERO_BITS_PERCENT: - return value::percent(0); - } - - auto data = repr_; - data &= ~PERCENT_BIT; - data += BIAS; - - if (repr_ & 0x40000000) { - return value::percent(std::bit_cast(data)); - } else { - return value::points(std::bit_cast(data)); - } - } - - bool isUndefined() const noexcept { - return ( - repr_ != AUTO_BITS && repr_ != ZERO_BITS_POINT && - repr_ != ZERO_BITS_PERCENT && std::isnan(std::bit_cast(repr_))); - } - - bool isDefined() const noexcept { - return !isUndefined(); - } - - bool isAuto() const noexcept { - return repr_ == AUTO_BITS; - } - - private: - template - static CompactValue of(float value) noexcept { - if (value == 0.0f || (value < LOWER_BOUND && value > -LOWER_BOUND)) { - constexpr auto zero = - UnitT == Unit::Percent ? ZERO_BITS_PERCENT : ZERO_BITS_POINT; - return {zero}; - } - - constexpr auto upperBound = - UnitT == Unit::Percent ? UPPER_BOUND_PERCENT : UPPER_BOUND_POINT; - if (value > upperBound || value < -upperBound) { - value = copysignf(upperBound, value); - } - - uint32_t unitBit = UnitT == Unit::Percent ? PERCENT_BIT : 0; - auto data = std::bit_cast(value); - data -= BIAS; - data |= unitBit; - return {data}; - } - - uint32_t repr_{0x7FC00000}; - - static constexpr uint32_t BIAS = 0x20000000; - static constexpr uint32_t PERCENT_BIT = 0x40000000; - - // these are signaling NaNs with specific bit pattern as payload they will be - // silenced whenever going through an FPU operation on ARM + x86 - static constexpr uint32_t AUTO_BITS = 0x7faaaaaa; - static constexpr uint32_t ZERO_BITS_POINT = 0x7f8f0f0f; - static constexpr uint32_t ZERO_BITS_PERCENT = 0x7f80f0f0; - - constexpr CompactValue(uint32_t data) noexcept : repr_(data) {} - - VISIBLE_FOR_TESTING uint32_t repr() { - return repr_; - } -}; - -template <> -CompactValue CompactValue::of(float) noexcept = delete; -template <> -CompactValue CompactValue::of(float) noexcept = delete; - -constexpr bool operator==(CompactValue a, CompactValue b) noexcept { - return a.repr_ == b.repr_; -} - -constexpr bool operator!=(CompactValue a, CompactValue b) noexcept { - return !(a == b); -} - -inline bool inexactEquals(CompactValue a, CompactValue b) { - return inexactEquals((StyleLength)a, (StyleLength)b); -} - -} // namespace facebook::yoga diff --git a/lib/yoga/src/main/cpp/yoga/style/SmallValueBuffer.h b/lib/yoga/src/main/cpp/yoga/style/SmallValueBuffer.h new file mode 100644 index 00000000000..2860a420f9c --- /dev/null +++ b/lib/yoga/src/main/cpp/yoga/style/SmallValueBuffer.h @@ -0,0 +1,133 @@ +/* + * 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 +#include +#include +#include +#include +#include + +namespace facebook::yoga { + +// Container which allows storing 32 or 64 bit integer values, whose index may +// never change. Values are first stored in a fixed buffer of `BufferSize` +// 32-bit chunks, before falling back to heap allocation. +template +class SmallValueBuffer { + public: + SmallValueBuffer() = default; + SmallValueBuffer(const SmallValueBuffer& other) { + *this = other; + } + SmallValueBuffer(SmallValueBuffer&& other) = default; + + // Add a new element to the buffer, returning the index of the element + uint16_t push(uint32_t value) { + const auto index = count_++; + assert(index < 4096 && "SmallValueBuffer can only hold up to 4096 chunks"); + if (index < buffer_.size()) { + buffer_[index] = value; + return index; + } + + if (overflow_ == nullptr) { + overflow_ = std::make_unique(); + } + + overflow_->buffer_.push_back(value); + overflow_->wideElements_.push_back(false); + return index; + } + + uint16_t push(uint64_t value) { + const auto lsb = static_cast(value & 0xFFFFFFFF); + const auto msb = static_cast(value >> 32); + + const auto lsbIndex = push(lsb); + [[maybe_unused]] const auto msbIndex = push(msb); + assert( + msbIndex < 4096 && "SmallValueBuffer can only hold up to 4096 chunks"); + + if (lsbIndex < buffer_.size()) { + wideElements_[lsbIndex] = true; + } else { + overflow_->wideElements_[lsbIndex - buffer_.size()] = true; + } + return lsbIndex; + } + + // Replace an existing element in the buffer with a new value. A new index + // may be returned, e.g. if a new value is wider than the previous. + [[nodiscard]] uint16_t replace(uint16_t index, uint32_t value) { + if (index < buffer_.size()) { + buffer_[index] = value; + } else { + overflow_->buffer_.at(index - buffer_.size()) = value; + } + + return index; + } + + [[nodiscard]] uint16_t replace(uint16_t index, uint64_t value) { + const bool isWide = index < wideElements_.size() + ? wideElements_[index] + : overflow_->wideElements_.at(index - buffer_.size()); + + if (isWide) { + const auto lsb = static_cast(value & 0xFFFFFFFF); + const auto msb = static_cast(value >> 32); + + [[maybe_unused]] auto lsbIndex = replace(index, lsb); + [[maybe_unused]] auto msbIndex = replace(index + 1, msb); + return index; + } else { + return push(value); + } + } + + // Get a value of a given width + uint32_t get32(uint16_t index) const { + if (index < buffer_.size()) { + return buffer_[index]; + } else { + return overflow_->buffer_.at(index - buffer_.size()); + } + } + + uint64_t get64(uint16_t index) const { + const auto lsb = get32(index); + const auto msb = get32(index + 1); + return (static_cast(msb) << 32) | lsb; + } + + SmallValueBuffer& operator=(const SmallValueBuffer& other) { + count_ = other.count_; + buffer_ = other.buffer_; + wideElements_ = other.wideElements_; + overflow_ = other.overflow_ ? std::make_unique(*other.overflow_) + : nullptr; + return *this; + } + + SmallValueBuffer& operator=(SmallValueBuffer&& other) = default; + + private: + struct Overflow { + std::vector buffer_; + std::vector wideElements_; + }; + + uint16_t count_{0}; + std::array buffer_; + std::bitset wideElements_; + std::unique_ptr overflow_; +}; + +} // namespace facebook::yoga diff --git a/lib/yoga/src/main/cpp/yoga/style/Style.h b/lib/yoga/src/main/cpp/yoga/style/Style.h index aae189a778d..028fcf17c1e 100644 --- a/lib/yoga/src/main/cpp/yoga/style/Style.h +++ b/lib/yoga/src/main/cpp/yoga/style/Style.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include #include @@ -29,8 +28,8 @@ #include #include #include -#include #include +#include namespace facebook::yoga { @@ -113,94 +112,94 @@ class YG_EXPORT Style { } FloatOptional flex() const { - return flex_; + return pool_.getNumber(flex_); } void setFlex(FloatOptional value) { - flex_ = value; + pool_.store(flex_, value); } FloatOptional flexGrow() const { - return flexGrow_; + return pool_.getNumber(flexGrow_); } void setFlexGrow(FloatOptional value) { - flexGrow_ = value; + pool_.store(flexGrow_, value); } FloatOptional flexShrink() const { - return flexShrink_; + return pool_.getNumber(flexShrink_); } void setFlexShrink(FloatOptional value) { - flexShrink_ = value; + pool_.store(flexShrink_, value); } Style::Length flexBasis() const { - return (Style::Length)flexBasis_; + return pool_.getLength(flexBasis_); } void setFlexBasis(Style::Length value) { - flexBasis_ = CompactValue(value); + pool_.store(flexBasis_, value); } Style::Length margin(Edge edge) const { - return (Style::Length)margin_[yoga::to_underlying(edge)]; + return pool_.getLength(margin_[yoga::to_underlying(edge)]); } void setMargin(Edge edge, Style::Length value) { - margin_[yoga::to_underlying(edge)] = CompactValue(value); + pool_.store(margin_[yoga::to_underlying(edge)], value); } Style::Length position(Edge edge) const { - return (Style::Length)position_[yoga::to_underlying(edge)]; + return pool_.getLength(position_[yoga::to_underlying(edge)]); } void setPosition(Edge edge, Style::Length value) { - position_[yoga::to_underlying(edge)] = CompactValue(value); + pool_.store(position_[yoga::to_underlying(edge)], value); } Style::Length padding(Edge edge) const { - return (Style::Length)padding_[yoga::to_underlying(edge)]; + return pool_.getLength(padding_[yoga::to_underlying(edge)]); } void setPadding(Edge edge, Style::Length value) { - padding_[yoga::to_underlying(edge)] = CompactValue(value); + pool_.store(padding_[yoga::to_underlying(edge)], value); } Style::Length border(Edge edge) const { - return (Style::Length)border_[yoga::to_underlying(edge)]; + return pool_.getLength(border_[yoga::to_underlying(edge)]); } void setBorder(Edge edge, Style::Length value) { - border_[yoga::to_underlying(edge)] = CompactValue(value); + pool_.store(border_[yoga::to_underlying(edge)], value); } Style::Length gap(Gutter gutter) const { - return (Style::Length)gap_[yoga::to_underlying(gutter)]; + return pool_.getLength(gap_[yoga::to_underlying(gutter)]); } void setGap(Gutter gutter, Style::Length value) { - gap_[yoga::to_underlying(gutter)] = CompactValue(value); + pool_.store(gap_[yoga::to_underlying(gutter)], value); } Style::Length dimension(Dimension axis) const { - return (Style::Length)dimensions_[yoga::to_underlying(axis)]; + return pool_.getLength(dimensions_[yoga::to_underlying(axis)]); } void setDimension(Dimension axis, Style::Length value) { - dimensions_[yoga::to_underlying(axis)] = CompactValue(value); + pool_.store(dimensions_[yoga::to_underlying(axis)], value); } Style::Length minDimension(Dimension axis) const { - return (Style::Length)minDimensions_[yoga::to_underlying(axis)]; + return pool_.getLength(minDimensions_[yoga::to_underlying(axis)]); } void setMinDimension(Dimension axis, Style::Length value) { - minDimensions_[yoga::to_underlying(axis)] = CompactValue(value); + pool_.store(minDimensions_[yoga::to_underlying(axis)], value); } Style::Length maxDimension(Dimension axis) const { - return (Style::Length)maxDimensions_[yoga::to_underlying(axis)]; + return pool_.getLength(maxDimensions_[yoga::to_underlying(axis)]); } void setMaxDimension(Dimension axis, Style::Length value) { - maxDimensions_[yoga::to_underlying(axis)] = CompactValue(value); + pool_.store(maxDimensions_[yoga::to_underlying(axis)], value); } FloatOptional aspectRatio() const { - return aspectRatio_; + return pool_.getNumber(aspectRatio_); } void setAspectRatio(FloatOptional value) { - aspectRatio_ = value; + pool_.store(aspectRatio_, value); } bool horizontalInsetsDefined() const { @@ -448,19 +447,21 @@ class YG_EXPORT Style { alignItems_ == other.alignItems_ && alignSelf_ == other.alignSelf_ && positionType_ == other.positionType_ && flexWrap_ == other.flexWrap_ && overflow_ == other.overflow_ && display_ == other.display_ && - inexactEquals(flex_, other.flex_) && - inexactEquals(flexGrow_, other.flexGrow_) && - inexactEquals(flexShrink_, other.flexShrink_) && - inexactEquals(flexBasis_, other.flexBasis_) && - inexactEquals(margin_, other.margin_) && - inexactEquals(position_, other.position_) && - inexactEquals(padding_, other.padding_) && - inexactEquals(border_, other.border_) && - inexactEquals(gap_, other.gap_) && - inexactEquals(dimensions_, other.dimensions_) && - inexactEquals(minDimensions_, other.minDimensions_) && - inexactEquals(maxDimensions_, other.maxDimensions_) && - inexactEquals(aspectRatio_, other.aspectRatio_); + numbersEqual(flex_, pool_, other.flex_, other.pool_) && + numbersEqual(flexGrow_, pool_, other.flexGrow_, other.pool_) && + numbersEqual(flexShrink_, pool_, other.flexShrink_, other.pool_) && + lengthsEqual(flexBasis_, pool_, other.flexBasis_, other.pool_) && + lengthsEqual(margin_, pool_, other.margin_, other.pool_) && + lengthsEqual(position_, pool_, other.position_, other.pool_) && + lengthsEqual(padding_, pool_, other.padding_, other.pool_) && + lengthsEqual(border_, pool_, other.border_, other.pool_) && + lengthsEqual(gap_, pool_, other.gap_, other.pool_) && + lengthsEqual(dimensions_, pool_, other.dimensions_, other.pool_) && + lengthsEqual( + minDimensions_, pool_, other.minDimensions_, other.pool_) && + lengthsEqual( + maxDimensions_, pool_, other.maxDimensions_, other.pool_) && + numbersEqual(aspectRatio_, pool_, other.aspectRatio_, other.pool_); } bool operator!=(const Style& other) const { @@ -468,23 +469,57 @@ class YG_EXPORT Style { } private: - using Dimensions = std::array()>; - using Edges = std::array()>; - using Gutters = std::array()>; + using Dimensions = std::array()>; + using Edges = std::array()>; + using Gutters = std::array()>; + + static inline bool numbersEqual( + const StyleValueHandle& lhsHandle, + const StyleValuePool& lhsPool, + const StyleValueHandle& rhsHandle, + const StyleValuePool& rhsPool) { + return (lhsHandle.isUndefined() && rhsHandle.isUndefined()) || + (lhsPool.getNumber(lhsHandle) == rhsPool.getNumber(rhsHandle)); + } + + static inline bool lengthsEqual( + const StyleValueHandle& lhsHandle, + const StyleValuePool& lhsPool, + const StyleValueHandle& rhsHandle, + const StyleValuePool& rhsPool) { + return (lhsHandle.isUndefined() && rhsHandle.isUndefined()) || + (lhsPool.getLength(lhsHandle) == rhsPool.getLength(rhsHandle)); + } + + template + static inline bool lengthsEqual( + const std::array& lhs, + const StyleValuePool& lhsPool, + const std::array& rhs, + const StyleValuePool& rhsPool) { + return std::equal( + lhs.begin(), + lhs.end(), + rhs.begin(), + rhs.end(), + [&](const auto& lhs, const auto& rhs) { + return lengthsEqual(lhs, lhsPool, rhs, rhsPool); + }); + } Style::Length computeColumnGap() const { if (gap_[yoga::to_underlying(Gutter::Column)].isDefined()) { - return (Style::Length)gap_[yoga::to_underlying(Gutter::Column)]; + return pool_.getLength(gap_[yoga::to_underlying(Gutter::Column)]); } else { - return (Style::Length)gap_[yoga::to_underlying(Gutter::All)]; + return pool_.getLength(gap_[yoga::to_underlying(Gutter::All)]); } } Style::Length computeRowGap() const { if (gap_[yoga::to_underlying(Gutter::Row)].isDefined()) { - return (Style::Length)gap_[yoga::to_underlying(Gutter::Row)]; + return pool_.getLength(gap_[yoga::to_underlying(Gutter::Row)]); } else { - return (Style::Length)gap_[yoga::to_underlying(Gutter::All)]; + return pool_.getLength(gap_[yoga::to_underlying(Gutter::All)]); } } @@ -492,27 +527,27 @@ class YG_EXPORT Style { const { if (layoutDirection == Direction::LTR && edges[yoga::to_underlying(Edge::Start)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Start)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Start)]); } else if ( layoutDirection == Direction::RTL && edges[yoga::to_underlying(Edge::End)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::End)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::End)]); } else if (edges[yoga::to_underlying(Edge::Left)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Left)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Left)]); } else if (edges[yoga::to_underlying(Edge::Horizontal)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Horizontal)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Horizontal)]); } else { - return (Style::Length)edges[yoga::to_underlying(Edge::All)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::All)]); } } Style::Length computeTopEdge(const Edges& edges) const { if (edges[yoga::to_underlying(Edge::Top)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Top)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Top)]); } else if (edges[yoga::to_underlying(Edge::Vertical)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Vertical)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Vertical)]); } else { - return (Style::Length)edges[yoga::to_underlying(Edge::All)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::All)]); } } @@ -520,27 +555,27 @@ class YG_EXPORT Style { const { if (layoutDirection == Direction::LTR && edges[yoga::to_underlying(Edge::End)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::End)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::End)]); } else if ( layoutDirection == Direction::RTL && edges[yoga::to_underlying(Edge::Start)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Start)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Start)]); } else if (edges[yoga::to_underlying(Edge::Right)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Right)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Right)]); } else if (edges[yoga::to_underlying(Edge::Horizontal)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Horizontal)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Horizontal)]); } else { - return (Style::Length)edges[yoga::to_underlying(Edge::All)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::All)]); } } Style::Length computeBottomEdge(const Edges& edges) const { if (edges[yoga::to_underlying(Edge::Bottom)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Bottom)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Bottom)]); } else if (edges[yoga::to_underlying(Edge::Vertical)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Vertical)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Vertical)]); } else { - return (Style::Length)edges[yoga::to_underlying(Edge::All)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::All)]); } } @@ -617,19 +652,23 @@ class YG_EXPORT Style { Overflow overflow_ : bitCount() = Overflow::Visible; Display display_ : bitCount() = Display::Flex; - FloatOptional flex_{}; - FloatOptional flexGrow_{}; - FloatOptional flexShrink_{}; - CompactValue flexBasis_{CompactValue::ofAuto()}; + StyleValueHandle flex_{}; + StyleValueHandle flexGrow_{}; + StyleValueHandle flexShrink_{}; + StyleValueHandle flexBasis_{StyleValueHandle::ofAuto()}; Edges margin_{}; Edges position_{}; Edges padding_{}; Edges border_{}; Gutters gap_{}; - Dimensions dimensions_{CompactValue::ofAuto(), CompactValue::ofAuto()}; + Dimensions dimensions_{ + StyleValueHandle::ofAuto(), + StyleValueHandle::ofAuto()}; Dimensions minDimensions_{}; Dimensions maxDimensions_{}; - FloatOptional aspectRatio_{}; + StyleValueHandle aspectRatio_{}; + + StyleValuePool pool_; }; } // namespace facebook::yoga diff --git a/lib/yoga/src/main/cpp/yoga/style/StyleValueHandle.h b/lib/yoga/src/main/cpp/yoga/style/StyleValueHandle.h new file mode 100644 index 00000000000..f4b97f0a9f7 --- /dev/null +++ b/lib/yoga/src/main/cpp/yoga/style/StyleValueHandle.h @@ -0,0 +1,98 @@ +/* + * 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 + +#include + +#include +#include +#include + +namespace facebook::yoga { + +#pragma pack(push) +#pragma pack(1) + +/** + * StyleValueHandle is a small (16-bit) handle to a length or number in a style. + * The value may be embedded directly in the handle if simple, or the handle may + * instead point to an index within a StyleValuePool. + * + * To read or write a value from a StyleValueHandle, use + * `StyleValuePool::store()`, and `StyleValuePool::getLength()`/ + * `StyleValuePool::getNumber()`. + */ +class StyleValueHandle { + public: + static constexpr StyleValueHandle ofAuto() { + StyleValueHandle handle; + handle.setType(Type::Auto); + return handle; + } + + constexpr bool isUndefined() const { + return type() == Type::Undefined; + } + + constexpr bool isDefined() const { + return !isUndefined(); + } + + constexpr bool isAuto() const { + return type() == Type::Auto; + } + + private: + friend class StyleValuePool; + + static constexpr uint16_t kHandleTypeMask = 0b0000'0000'0000'0111; + static constexpr uint16_t kHandleIndexedMask = 0b0000'0000'0000'1000; + static constexpr uint16_t kHandleValueMask = 0b1111'1111'1111'0000; + + enum class Type : uint8_t { + Undefined, + Point, + Percent, + Number, + Auto, + }; + + constexpr Type type() const { + return static_cast(repr_ & kHandleTypeMask); + } + + constexpr void setType(Type handleType) { + repr_ &= (~kHandleTypeMask); + repr_ |= static_cast(handleType); + } + + constexpr uint16_t value() const { + return repr_ >> 4; + } + + constexpr void setValue(uint16_t value) { + repr_ &= (~kHandleValueMask); + repr_ |= (value << 4); + } + + constexpr bool isValueIndexed() const { + return (repr_ & kHandleIndexedMask) != 0; + } + + constexpr void setValueIsIndexed() { + repr_ |= kHandleIndexedMask; + } + + uint16_t repr_{0}; +}; + +#pragma pack(pop) + +} // namespace facebook::yoga diff --git a/lib/yoga/src/main/cpp/yoga/style/StyleValuePool.h b/lib/yoga/src/main/cpp/yoga/style/StyleValuePool.h new file mode 100644 index 00000000000..f8ee93e47b3 --- /dev/null +++ b/lib/yoga/src/main/cpp/yoga/style/StyleValuePool.h @@ -0,0 +1,127 @@ +/* + * 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 +#include + +#include +#include +#include +#include + +namespace facebook::yoga { + +/** + * StyleValuePool allows compact storage for a sparse collection of assigned + * lengths and numbers. Values are referred to using StyleValueHandle. In most + * cases StyleValueHandle can embed the value directly, but if not, the value is + * stored within a buffer provided by the pool. The pool contains a fixed number + * of inline slots before falling back to heap allocating additional slots. + */ +class StyleValuePool { + public: + void store(StyleValueHandle& handle, StyleLength length) { + if (length.isUndefined()) { + handle.setType(StyleValueHandle::Type::Undefined); + } else if (length.isAuto()) { + handle.setType(StyleValueHandle::Type::Auto); + } else { + auto type = length.unit() == Unit::Point + ? StyleValueHandle::Type::Point + : StyleValueHandle::Type::Percent; + storeValue(handle, length.value().unwrap(), type); + } + } + + void store(StyleValueHandle& handle, FloatOptional number) { + if (number.isUndefined()) { + handle.setType(StyleValueHandle::Type::Undefined); + } else { + storeValue(handle, number.unwrap(), StyleValueHandle::Type::Number); + } + } + + StyleLength getLength(StyleValueHandle handle) const { + if (handle.isUndefined()) { + return value::undefined(); + } else if (handle.isAuto()) { + return value::ofAuto(); + } else { + assert( + handle.type() == StyleValueHandle::Type::Point || + handle.type() == StyleValueHandle::Type::Percent); + float value = (handle.isValueIndexed()) + ? std::bit_cast(buffer_.get32(handle.value())) + : unpackInlineInteger(handle.value()); + + return handle.type() == StyleValueHandle::Type::Point + ? value::points(value) + : value::percent(value); + } + } + + FloatOptional getNumber(StyleValueHandle handle) const { + if (handle.isUndefined()) { + return FloatOptional{}; + } else { + assert(handle.type() == StyleValueHandle::Type::Number); + float value = (handle.isValueIndexed()) + ? std::bit_cast(buffer_.get32(handle.value())) + : unpackInlineInteger(handle.value()); + return FloatOptional{value}; + } + } + + private: + void storeValue( + StyleValueHandle& handle, + float value, + StyleValueHandle::Type type) { + handle.setType(type); + + if (handle.isValueIndexed()) { + auto newIndex = + buffer_.replace(handle.value(), std::bit_cast(value)); + handle.setValue(newIndex); + } else if (isIntegerPackable(value)) { + handle.setValue(packInlineInteger(value)); + } else { + auto newIndex = buffer_.push(std::bit_cast(value)); + handle.setValue(newIndex); + handle.setValueIsIndexed(); + } + } + + static constexpr bool isIntegerPackable(float f) { + constexpr uint16_t kMaxInlineAbsValue = (1 << 11) - 1; + + int32_t i = static_cast(f); + return static_cast(i) == f && i >= -kMaxInlineAbsValue && + i <= +kMaxInlineAbsValue; + } + + static constexpr uint16_t packInlineInteger(float value) { + uint16_t isNegative = value < 0 ? 1 : 0; + return static_cast( + (isNegative << 11) | + (static_cast(value) * (isNegative ? -1 : 1))); + } + + static constexpr float unpackInlineInteger(uint16_t value) { + constexpr uint16_t kValueSignMask = 0b0000'1000'0000'0000; + constexpr uint16_t kValueMagnitudeMask = 0b0000'0111'1111'1111; + const bool isNegative = (value & kValueSignMask) != 0; + return static_cast( + (value & kValueMagnitudeMask) * (isNegative ? -1 : 1)); + } + + SmallValueBuffer<4> buffer_; +}; + +} // namespace facebook::yoga