Skip to content

Commit d5ab0f4

Browse files
committed
Prepare platform transition routing for more properties
1 parent b49e37f commit d5ab0f4

10 files changed

Lines changed: 274 additions & 45 deletions

File tree

packages/react-native-reanimated/Common/cpp/reanimated/CSS/common/values/CSSColor.cpp

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <reanimated/CSS/common/values/CSSColor.h>
22
#include <reanimated/CSS/svg/values/SVGBrush.h>
3+
#include <reanimated/CSS/utils/props.h>
34

45
#include <utility>
56

@@ -17,24 +18,7 @@ CSSColorBase<TColorType, TDerived>::CSSColorBase(TColorType colorType) : channel
1718

1819
template <ColorTypeEnum TColorType, typename TDerived>
1920
CSSColorBase<TColorType, TDerived>::CSSColorBase(int64_t numberValue)
20-
: channels{0, 0, 0, 0}, colorType(TColorType::Rgba) {
21-
uint32_t color;
22-
// On Android, colors are represented as signed 32-bit integers. In JS, we use
23-
// a bitwise operation (normalizedColor = normalizedColor | 0x0) to ensure the
24-
// value is treated as a signed int, causing numbers above 2^31 to become
25-
// negative. To correctly interpret these in C++, we cast negative values to
26-
// int32_t to preserve their bit pattern, then assign to uint32_t. This wraps
27-
// the bits (modulo 2^32), effectively reversing the JS-side bit shift.
28-
if (numberValue < 0) {
29-
color = static_cast<int32_t>(numberValue);
30-
} else {
31-
color = static_cast<uint32_t>(numberValue);
32-
}
33-
channels[0] = (color >> 16) & 0xFF; // Red
34-
channels[1] = (color >> 8) & 0xFF; // Green
35-
channels[2] = color & 0xFF; // Blue
36-
channels[3] = (color >> 24) & 0xFF; // Alpha
37-
}
21+
: channels(extractColorChannels(numberValue)), colorType(TColorType::Rgba) {}
3822

3923
template <ColorTypeEnum TColorType, typename TDerived>
4024
CSSColorBase<TColorType, TDerived>::CSSColorBase(bool value)

packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/transition/CSSPlatformTransition.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <reanimated/CSS/utils/reversingShortening.h>
44

55
#include <utility>
6+
#include <variant>
67
#include <vector>
78

89
namespace reanimated::css {
@@ -24,10 +25,13 @@ void CSSPlatformTransition::run(jsi::Runtime &rt, const CSSPlatformTransitionCon
2425

2526
void CSSPlatformTransition::run(const PropertyValueDynamicDiffsMap &propertiesDiffs, const double timestamp) {
2627
for (const auto &[propertyName, propertyDiff] : propertiesDiffs) {
27-
const auto &settings = settings_.at(propertyName);
28+
const auto settingsIt = settings_.find(propertyName);
29+
if (settingsIt == settings_.end()) {
30+
continue;
31+
}
2832
auto fromValue = parsePlatformValue(propertyName, propertyDiff.first);
2933
auto toValue = parsePlatformValue(propertyName, propertyDiff.second);
30-
applyEntry(propertyName, fromValue, toValue, settings, timestamp);
34+
applyEntry(propertyName, fromValue, toValue, settingsIt->second, timestamp);
3135
}
3236
}
3337

@@ -59,14 +63,21 @@ void CSSPlatformTransition::applyEntry(
5963
PlatformValue toValue,
6064
const CSSTransitionPropertySettings &settings,
6165
const double timestamp) {
66+
// Values that failed to parse must never reach the renderer (it throws on
67+
// monostate). This can happen on the dynamic-diffs path, which bypasses the
68+
// value-shape routing check.
69+
if (std::holds_alternative<std::monostate>(fromValue) || std::holds_alternative<std::monostate>(toValue)) {
70+
return;
71+
}
6272
const auto activeIt = activeProperties_.find(propertyName);
6373
auto *prev =
6474
activeIt != activeProperties_.end() && toValue == activeIt->second.adjustedStart ? &activeIt->second : nullptr;
6575
auto rs = prev ? reverseShorten(prev->previous, timestamp, settings.duration, settings.delay, settings.easingConfig)
6676
: makeReversingState(timestamp, settings.duration, settings.delay, settings.easingConfig);
6777

68-
proxy_->run(CSSPlatformTransitionPropertyConfig{
69-
viewTag_, propertyName, fromValue, toValue, rs.duration, rs.startTimestamp, settings.easingConfig});
78+
proxy_->run(
79+
CSSPlatformTransitionPropertyConfig{
80+
viewTag_, propertyName, fromValue, toValue, rs.duration, rs.startTimestamp, settings.easingConfig});
7081

7182
activeProperties_[propertyName] = ActiveProperty{
7283
prev ? prev->adjustedEnd : fromValue,

packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/transition/CSSPlatformTransitionProxy.cpp

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
#include <reanimated/CSS/core/transition/CSSPlatformTransitionProxy.h>
2-
#include <reanimated/Tools/FeatureFlags.h>
2+
#include <reanimated/CSS/utils/platform.h>
3+
#include <reanimated/CSS/utils/props.h>
34

5+
#include <unordered_set>
46
#include <utility>
7+
#include <variant>
58

69
namespace reanimated::css {
710

11+
namespace {
12+
13+
// Both endpoints must be expressible as platform values; anything the parser
14+
// cannot convert (percent lengths, platform-specific color objects, ...) must
15+
// run on the loop instead.
16+
bool valueShapeRoutable(
17+
jsi::Runtime &rt,
18+
const std::string &propertyName,
19+
const jsi::Value &fromValue,
20+
const jsi::Value &toValue) {
21+
return !std::holds_alternative<std::monostate>(parsePlatformValue(rt, propertyName, fromValue)) &&
22+
!std::holds_alternative<std::monostate>(parsePlatformValue(rt, propertyName, toValue));
23+
}
24+
25+
} // namespace
26+
827
CSSPlatformTransitionProxy::CSSPlatformTransitionProxy(
928
CSSCanRoutePropertyFunction canRoute,
1029
CSSApplyTransitionFunction applyTransition,
@@ -14,9 +33,6 @@ CSSPlatformTransitionProxy::CSSPlatformTransitionProxy(
1433
removeTransition_(std::move(removeTransition)) {}
1534

1635
bool CSSPlatformTransitionProxy::canRoute(const std::string &propertyName, const EasingConfig &easing) const {
17-
if constexpr (!StaticFeatureFlags::getFlag("IOS_CSS_CORE_ANIMATION")) {
18-
return false;
19-
}
2036
return canRoute_ && canRoute_(propertyName, easing);
2137
}
2238

@@ -37,11 +53,19 @@ void CSSPlatformTransitionProxy::remove(const Tag viewTag, const std::string &pr
3753
// each prop; erasing from the *other* side's set returns nonzero exactly when
3854
// the prop is migrating sides - that's how we emit cancels.
3955
CSSPlatformTransitionProxy::ProcessedConfig CSSPlatformTransitionProxy::processConfig(
56+
jsi::Runtime &rt,
4057
CSSTransitionConfig &&config,
4158
const CSSTransitionRouting &previousRouting) const {
4259
ProcessedConfig result;
4360
result.routing = previousRouting;
4461

62+
// The sibling-aware border check below needs to see all transitioning props.
63+
std::unordered_set<std::string> transitioningNames;
64+
transitioningNames.reserve(config.changedPropertiesSettings.size());
65+
for (const auto &entry : config.changedPropertiesSettings) {
66+
transitioningNames.insert(entry.first);
67+
}
68+
4569
// Drain changedPropertiesSettings; for each, decide routing and bucket.
4670
// extract() preserves move-only PropertyValueDiff (jsi::Value pair).
4771
while (!config.changedPropertiesSettings.empty()) {
@@ -50,7 +74,14 @@ CSSPlatformTransitionProxy::ProcessedConfig CSSPlatformTransitionProxy::processC
5074
const auto &settings = settingsNode.mapped();
5175
const auto valueIt = config.changedProperties.find(propertyName);
5276

53-
if (canRoute(propertyName, settings.easingConfig)) {
77+
// Settings-only entries (no value diff) are checked by name and easing
78+
// only; their values arrive later through the dynamic-diffs path, which
79+
// re-parses and skips anything inexpressible.
80+
const bool valuesRoutable = valueIt == config.changedProperties.end() ||
81+
valueShapeRoutable(rt, propertyName, valueIt->second.first, valueIt->second.second);
82+
83+
if (canRoute(propertyName, settings.easingConfig) &&
84+
!hasNonUniformBorderSibling(propertyName, transitioningNames) && valuesRoutable) {
5485
// loop -> platform migration: cancel on loop.
5586
if (result.routing.loop.erase(propertyName) > 0) {
5687
result.loop.removedProperties.push_back(propertyName);

packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/transition/CSSPlatformTransitionProxy.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,12 @@ class CSSPlatformTransitionProxy {
7070

7171
// Filters the incoming config into loop / platform buckets and emits implicit
7272
// cancels on the old side when a property migrates compared to previousRouting.
73-
// The platform side does its own value parsing inside CSSPlatformTransition::run -
74-
// the proxy only forwards the raw jsi value pair via the raw entry.
75-
ProcessedConfig processConfig(CSSTransitionConfig &&config, const CSSTransitionRouting &previousRouting) const;
73+
// A property routes to the platform only when the platform declares it
74+
// routable (canRoute), no non-uniform border sibling is transitioning along
75+
// with it, and both endpoint values are expressible as platform values;
76+
// otherwise it goes to the loop, which can animate everything.
77+
ProcessedConfig
78+
processConfig(jsi::Runtime &rt, CSSTransitionConfig &&config, const CSSTransitionRouting &previousRouting) const;
7679

7780
private:
7881
bool canRoute(const std::string &propertyName, const EasingConfig &easing) const;

packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/transition/CSSTransition.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ CSSTransition::CSSTransition(
1414
Observer &observer,
1515
const std::shared_ptr<CSSPlatformTransitionProxy> &platformTransitionProxy)
1616
: shadowNode_(std::move(shadowNode)),
17-
loopTransition_(std::make_shared<CSSLoopTransition>(
18-
shadowNode_->getTag(),
19-
shadowNode_->getComponentName(),
20-
viewStylesRepository,
21-
observer)),
17+
loopTransition_(
18+
std::make_shared<CSSLoopTransition>(
19+
shadowNode_->getTag(),
20+
shadowNode_->getComponentName(),
21+
viewStylesRepository,
22+
observer)),
2223
platformTransitionProxy_(platformTransitionProxy) {}
2324

2425
CSSTransition::~CSSTransition() {
@@ -89,7 +90,7 @@ void CSSTransition::updateSettings(
8990

9091
CSSTransitionConfig
9192
CSSTransition::splitForPlatformRouting(jsi::Runtime &rt, CSSTransitionConfig &&config, const double timestamp) {
92-
auto processed = platformTransitionProxy_->processConfig(std::move(config), routing_);
93+
auto processed = platformTransitionProxy_->processConfig(rt, std::move(config), routing_);
9394
routing_ = std::move(processed.routing);
9495

9596
if (!processed.platform.changedPropertiesSettings.empty() || !processed.platform.removedProperties.empty()) {

packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/platform.cpp

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,114 @@
11
#include <reanimated/CSS/utils/platform.h>
22

3+
#include <reanimated/CSS/utils/props.h>
4+
5+
#include <cstdint>
36
#include <unordered_map>
47

58
namespace reanimated::css {
69

710
namespace {
811

912
// CSS-spec defaults; mirror Common/cpp/reanimated/CSS/InterpolatorRegistry.cpp.
13+
// Used when an endpoint is null/undefined - the property's default value
14+
// stands in for the missing side.
1015
const std::unordered_map<std::string, PlatformValue> kDefaults = {
1116
{"opacity", 1.0},
17+
{"borderWidth", 0.0},
18+
{"borderRadius", 0.0},
19+
{"shadowOpacity", 1.0},
20+
{"shadowRadius", 0.0},
21+
{"backgroundColor", std::array<double, 4>{0.0, 0.0, 0.0, 0.0}},
22+
{"borderColor", std::array<double, 4>{0.0, 0.0, 0.0, 1.0}},
23+
{"shadowColor", std::array<double, 4>{0.0, 0.0, 0.0, 1.0}},
24+
{"shadowOffset", std::array<double, 2>{0.0, 0.0}},
1225
};
1326

14-
PlatformValue parseDouble(const jsi::Value &value) {
15-
return value.isNumber() ? PlatformValue(value.asNumber()) : PlatformValue{};
27+
bool isScalarProperty(const std::string &name) {
28+
return name == "opacity" || name == "borderWidth" || name == "borderRadius" || name == "shadowOpacity" ||
29+
name == "shadowRadius";
30+
}
31+
32+
bool isColorProperty(const std::string &name) {
33+
return name == "backgroundColor" || name == "borderColor" || name == "shadowColor";
34+
}
35+
36+
PlatformValue defaultValueFor(const std::string &propertyName) {
37+
const auto it = kDefaults.find(propertyName);
38+
return it != kDefaults.end() ? it->second : PlatformValue{};
39+
}
40+
41+
// RN's processColor pre-bakes named colors and rgba() strings into a packed
42+
// ARGB number. PlatformColor-like values arrive as objects instead - those are
43+
// rejected (monostate) so the caller falls back to the loop path.
44+
PlatformValue parseColorNumber(const double number) {
45+
const auto channels = extractColorChannels(static_cast<int64_t>(number));
46+
return std::array<double, 4>{
47+
channels[0] / 255.0,
48+
channels[1] / 255.0,
49+
channels[2] / 255.0,
50+
channels[3] / 255.0,
51+
};
52+
}
53+
54+
// Missing width/height components default to 0, matching the loop-path record
55+
// interpolator semantics.
56+
PlatformValue parseSizeValue(jsi::Runtime &rt, const jsi::Value &value) {
57+
if (!value.isObject()) {
58+
return PlatformValue{};
59+
}
60+
const auto obj = value.asObject(rt);
61+
const auto width = obj.getProperty(rt, "width");
62+
const auto height = obj.getProperty(rt, "height");
63+
return std::array<double, 2>{
64+
width.isNumber() ? width.asNumber() : 0.0,
65+
height.isNumber() ? height.asNumber() : 0.0,
66+
};
67+
}
68+
69+
PlatformValue parseSizeValue(const folly::dynamic &value) {
70+
if (!value.isObject()) {
71+
return PlatformValue{};
72+
}
73+
const auto *width = value.get_ptr("width");
74+
const auto *height = value.get_ptr("height");
75+
return std::array<double, 2>{
76+
width != nullptr && width->isNumber() ? width->asDouble() : 0.0,
77+
height != nullptr && height->isNumber() ? height->asDouble() : 0.0,
78+
};
1679
}
1780

1881
} // namespace
1982

2083
PlatformValue parsePlatformValue(jsi::Runtime &rt, const std::string &propertyName, const jsi::Value &value) {
2184
if (value.isNull() || value.isUndefined()) {
22-
const auto it = kDefaults.find(propertyName);
23-
return it != kDefaults.end() ? it->second : PlatformValue{};
85+
return defaultValueFor(propertyName);
2486
}
25-
if (propertyName == "opacity") {
26-
return parseDouble(value);
87+
if (isScalarProperty(propertyName)) {
88+
return value.isNumber() ? PlatformValue(value.asNumber()) : PlatformValue{};
89+
}
90+
if (isColorProperty(propertyName)) {
91+
return value.isNumber() ? parseColorNumber(value.asNumber()) : PlatformValue{};
92+
}
93+
if (propertyName == "shadowOffset") {
94+
return parseSizeValue(rt, value);
2795
}
2896
return PlatformValue{};
2997
}
3098

3199
PlatformValue parsePlatformValue(const std::string &propertyName, const folly::dynamic &value) {
32100
if (value.isNull()) {
33-
const auto it = kDefaults.find(propertyName);
34-
return it != kDefaults.end() ? it->second : PlatformValue{};
101+
return defaultValueFor(propertyName);
35102
}
36-
if (propertyName == "opacity") {
103+
if (isScalarProperty(propertyName)) {
37104
return value.isNumber() ? PlatformValue(value.asDouble()) : PlatformValue{};
38105
}
106+
if (isColorProperty(propertyName)) {
107+
return value.isNumber() ? parseColorNumber(value.asDouble()) : PlatformValue{};
108+
}
109+
if (propertyName == "shadowOffset") {
110+
return parseSizeValue(value);
111+
}
39112
return PlatformValue{};
40113
}
41114

packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/platform.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ namespace reanimated::css {
1111

1212
using namespace facebook;
1313

14+
// Platform-neutral value shapes the renderers convert to their native
15+
// animation types. The array sizes double as type tags:
16+
// - double : scalar properties (opacity, borderWidth, ...)
17+
// - array<double, 2> : width/height size (shadowOffset)
18+
// - array<double, 4> : RGBA color, normalized [0, 1]
19+
// - monostate : not expressible; the caller must fall back to the loop
1420
using PlatformValue = std::variant<std::monostate, double, std::array<double, 2>, std::array<double, 4>>;
1521

1622
PlatformValue parsePlatformValue(jsi::Runtime &rt, const std::string &propertyName, const jsi::Value &value);

0 commit comments

Comments
 (0)