Skip to content

Commit e1ae440

Browse files
committed
Prepare platform transition routing for more properties
1 parent e937206 commit e1ae440

18 files changed

Lines changed: 371 additions & 208 deletions

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: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,22 @@ CSSPlatformTransition::CSSPlatformTransition(
1212
const std::shared_ptr<CSSPlatformTransitionProxy> &proxy)
1313
: viewTag_(viewTag), proxy_(proxy) {}
1414

15-
void CSSPlatformTransition::run(jsi::Runtime &rt, const CSSPlatformTransitionConfig &config, const double timestamp) {
15+
void CSSPlatformTransition::run(const CSSPlatformTransitionConfig &config, const double timestamp) {
1616
for (const auto &entry : config.changedProperties) {
17-
runEntry(rt, entry, timestamp);
17+
applyEntry(entry.propertyName, entry.fromValue, entry.toValue, entry.settings, timestamp);
1818
}
1919

2020
for (const auto &propertyName : config.removedProperties) {
2121
cancel(propertyName);
2222
}
2323
}
2424

25-
void CSSPlatformTransition::run(const PropertyValueDynamicDiffsMap &propertiesDiffs, const double timestamp) {
25+
void CSSPlatformTransition::run(const ParsedPlatformDiffs &propertiesDiffs, const double timestamp) {
2626
for (const auto &[propertyName, propertyDiff] : propertiesDiffs) {
27+
// The routing layer only sends properties registered here, so missing
28+
// settings are an invariant violation.
2729
const auto &settings = settings_.at(propertyName);
28-
auto fromValue = parsePlatformValue(propertyName, propertyDiff.first);
29-
auto toValue = parsePlatformValue(propertyName, propertyDiff.second);
30-
applyEntry(propertyName, fromValue, toValue, settings, timestamp);
30+
applyEntry(propertyName, propertyDiff.first, propertyDiff.second, settings, timestamp);
3131
}
3232
}
3333

@@ -42,17 +42,6 @@ void CSSPlatformTransition::updateSettings(
4242
}
4343
}
4444

45-
void CSSPlatformTransition::runEntry(
46-
jsi::Runtime &rt,
47-
const CSSPlatformTransitionRawEntry &entry,
48-
const double timestamp) {
49-
// null/undefined endpoints are resolved to the property's default inside
50-
// parsePlatformValue; type mismatches and unsupported properties throw.
51-
auto fromValue = parsePlatformValue(rt, entry.propertyName, entry.valueDiff.first);
52-
auto toValue = parsePlatformValue(rt, entry.propertyName, entry.valueDiff.second);
53-
applyEntry(entry.propertyName, fromValue, toValue, entry.settings, timestamp);
54-
}
55-
5645
void CSSPlatformTransition::applyEntry(
5746
const std::string &propertyName,
5847
PlatformValue fromValue,

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
#pragma once
22

33
#include <reanimated/CSS/core/transition/CSSPlatformTransitionProxy.h>
4-
#include <reanimated/CSS/utils/platform.h>
54
#include <reanimated/CSS/utils/reversingShortening.h>
65

7-
#include <jsi/jsi.h>
86
#include <react/renderer/core/ReactPrimitives.h>
97

108
#include <memory>
@@ -23,9 +21,8 @@ class CSSPlatformTransition {
2321

2422
CSSPlatformTransition(const CSSPlatformTransition &) = delete;
2523

26-
void run(jsi::Runtime &rt, const CSSPlatformTransitionConfig &config, double timestamp);
27-
/** TODO: unify folly::dynamic and jsi::value versions */
28-
void run(const PropertyValueDynamicDiffsMap &propertiesDiffs, double timestamp);
24+
void run(const CSSPlatformTransitionConfig &config, double timestamp);
25+
void run(const ParsedPlatformDiffs &propertiesDiffs, double timestamp);
2926

3027
void updateSettings(
3128
const PropertiesSettingsMap &changedPropertiesSettings,
@@ -41,7 +38,6 @@ class CSSPlatformTransition {
4138
ReversingState previous;
4239
};
4340

44-
void runEntry(jsi::Runtime &rt, const CSSPlatformTransitionRawEntry &entry, double timestamp);
4541
void applyEntry(
4642
const std::string &propertyName,
4743
PlatformValue fromValue,

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

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,69 @@
11
#include <reanimated/CSS/core/transition/CSSPlatformTransitionProxy.h>
2-
#include <reanimated/Tools/FeatureFlags.h>
32

43
#include <react/debug/react_native_assert.h>
54

65
#include <utility>
76

87
namespace reanimated::css {
98

9+
namespace {
10+
11+
// Reduces a jsi::Value transition endpoint to the representation-agnostic shape
12+
// the platform parses (a number, a size, or monostate for null / absent).
13+
CSSEndpointValue endpointFromJSI(jsi::Runtime &rt, const jsi::Value &value) {
14+
if (value.isNumber()) {
15+
return value.asNumber();
16+
}
17+
if (value.isObject()) {
18+
const auto object = value.asObject(rt);
19+
const auto width = object.getProperty(rt, "width");
20+
const auto height = object.getProperty(rt, "height");
21+
return std::array<double, 2>{
22+
width.isNumber() ? width.asNumber() : 0.0,
23+
height.isNumber() ? height.asNumber() : 0.0,
24+
};
25+
}
26+
return std::monostate{};
27+
}
28+
29+
// The same reduction for the runtime-free pseudo-selector toggle path.
30+
CSSEndpointValue endpointFromDynamic(const folly::dynamic &value) {
31+
if (value.isNumber()) {
32+
return value.asDouble();
33+
}
34+
if (value.isObject()) {
35+
const auto *width = value.get_ptr("width");
36+
const auto *height = value.get_ptr("height");
37+
return std::array<double, 2>{
38+
width != nullptr && width->isNumber() ? width->asDouble() : 0.0,
39+
height != nullptr && height->isNumber() ? height->asDouble() : 0.0,
40+
};
41+
}
42+
return std::monostate{};
43+
}
44+
45+
} // namespace
46+
1047
CSSPlatformTransitionProxy::CSSPlatformTransitionProxy(
1148
CSSCanRoutePropertyFunction canRoute,
49+
CSSParseValueFunction parseValue,
1250
CSSApplyTransitionFunction applyTransition,
1351
CSSRemoveTransitionFunction removeTransition)
1452
: canRoute_(std::move(canRoute)),
53+
parseValue_(std::move(parseValue)),
1554
applyTransition_(std::move(applyTransition)),
1655
removeTransition_(std::move(removeTransition)) {}
1756

1857
bool CSSPlatformTransitionProxy::canRoute(const std::string &propertyName, const EasingConfig &easing) const {
19-
if constexpr (!StaticFeatureFlags::getFlag("IOS_CSS_CORE_ANIMATION")) {
20-
return false;
21-
}
2258
return canRoute_ && canRoute_(propertyName, easing);
2359
}
2460

61+
std::optional<PlatformValue> CSSPlatformTransitionProxy::parseValue(
62+
const std::string &propertyName,
63+
const CSSEndpointValue &value) const {
64+
return parseValue_ ? parseValue_(propertyName, value) : std::nullopt;
65+
}
66+
2567
void CSSPlatformTransitionProxy::run(const CSSPlatformTransitionPropertyConfig &config) const {
2668
if (applyTransition_) {
2769
applyTransition_(config);
@@ -34,35 +76,42 @@ void CSSPlatformTransitionProxy::remove(const Tag viewTag, const std::string &pr
3476
}
3577
}
3678

37-
// Splits the new split-shape config into loop / platform buckets. result.routing
38-
// starts as a copy of the previous call's routing and is updated as we route
39-
// each prop; erasing from the *other* side's set returns nonzero exactly when
40-
// the prop is migrating sides - that's how we emit cancels.
79+
// Erasing from the other side's routing set returns nonzero exactly when the
80+
// property migrates sides - that's when the old side gets a cancel.
4181
CSSPlatformTransitionProxy::ProcessedConfig CSSPlatformTransitionProxy::processConfig(
82+
jsi::Runtime &rt,
4283
CSSTransitionConfig &&config,
4384
const CSSTransitionRouting &previousRouting) const {
4485
ProcessedConfig result;
4586
result.routing = previousRouting;
4687

47-
// Drain changedPropertiesSettings; for each, decide routing and bucket.
48-
// extract() preserves move-only PropertyValueDiff (jsi::Value pair).
88+
// extract() preserves the move-only jsi::Value diffs.
4989
while (!config.changedPropertiesSettings.empty()) {
5090
auto settingsNode = config.changedPropertiesSettings.extract(config.changedPropertiesSettings.begin());
5191
const auto &propertyName = settingsNode.key();
5292
const auto &settings = settingsNode.mapped();
5393
const auto valueIt = config.changedProperties.find(propertyName);
5494

55-
if (canRoute(propertyName, settings.easingConfig)) {
95+
bool routable = canRoute(propertyName, settings.easingConfig);
96+
std::optional<PlatformValue> fromValue;
97+
std::optional<PlatformValue> toValue;
98+
if (routable && valueIt != config.changedProperties.end()) {
99+
fromValue = parseValue(propertyName, endpointFromJSI(rt, valueIt->second.first));
100+
toValue = parseValue(propertyName, endpointFromJSI(rt, valueIt->second.second));
101+
routable = fromValue.has_value() && toValue.has_value();
102+
}
103+
104+
if (routable) {
56105
// loop -> platform migration: cancel on loop.
57106
if (result.routing.loop.erase(propertyName) > 0) {
58107
result.loop.removedProperties.push_back(propertyName);
59108
}
60109
result.routing.platform.insert(propertyName);
61110

62111
if (valueIt != config.changedProperties.end()) {
63-
auto valueNode = config.changedProperties.extract(valueIt);
112+
config.changedProperties.erase(valueIt);
64113
result.platform.changedProperties.push_back(
65-
CSSPlatformTransitionRawEntry{propertyName, std::move(valueNode.mapped()), settings});
114+
CSSPlatformTransitionEntry{propertyName, *fromValue, *toValue, settings});
66115
}
67116
result.platform.changedPropertiesSettings.insert(std::move(settingsNode));
68117
} else {
@@ -84,8 +133,7 @@ CSSPlatformTransitionProxy::ProcessedConfig CSSPlatformTransitionProxy::processC
84133
// have consumed all of changedProperties.
85134
react_native_assert(config.changedProperties.empty() && "[Reanimated] CSS transition value diff without settings");
86135

87-
// Props JS asked to stop transitioning: look up the owning side in routing
88-
// and forward the cancel there.
136+
// Forward removal cancels to the side that owns the property.
89137
for (auto &propertyName : config.removedProperties) {
90138
if (result.routing.platform.erase(propertyName) > 0) {
91139
result.platform.removedProperties.push_back(std::move(propertyName));
@@ -97,4 +145,25 @@ CSSPlatformTransitionProxy::ProcessedConfig CSSPlatformTransitionProxy::processC
97145
return result;
98146
}
99147

148+
CSSPlatformTransitionProxy::ProcessedDynamicDiffs CSSPlatformTransitionProxy::processDynamicDiffs(
149+
const CSSTransitionRouting &routing,
150+
const PropertyValueDynamicDiffsMap &propertyDiffs) const {
151+
ProcessedDynamicDiffs result;
152+
for (const auto &[propertyName, propertyDiff] : propertyDiffs) {
153+
// The routing was decided when the transition was registered; here we only
154+
// re-parse the toggled endpoints. Values the platform cannot express fall
155+
// back to the loop.
156+
if (routing.platform.contains(propertyName)) {
157+
auto fromValue = parseValue(propertyName, endpointFromDynamic(propertyDiff.first));
158+
auto toValue = parseValue(propertyName, endpointFromDynamic(propertyDiff.second));
159+
if (fromValue && toValue) {
160+
result.platform.emplace(propertyName, std::make_pair(*fromValue, *toValue));
161+
continue;
162+
}
163+
}
164+
result.loop.emplace(propertyName, propertyDiff);
165+
}
166+
return result;
167+
}
168+
100169
} // namespace reanimated::css

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

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,42 @@
33
#include <reanimated/CSS/common/definitions.h>
44
#include <reanimated/CSS/configs/CSSTransitionConfig.h>
55
#include <reanimated/CSS/easing/EasingConfigs.h>
6-
#include <reanimated/CSS/utils/platform.h>
76

7+
#include <folly/dynamic.h>
88
#include <jsi/jsi.h>
99
#include <react/renderer/core/ReactPrimitives.h>
1010

11+
#include <array>
1112
#include <functional>
13+
#include <optional>
1214
#include <string>
15+
#include <unordered_map>
16+
#include <utility>
17+
#include <variant>
1318
#include <vector>
1419

20+
// PlatformValue is platform-defined: each platform sets the value shapes it can
21+
// animate natively; shared code only stores and moves values from parse hooks.
22+
#ifdef __APPLE__
23+
#include <reanimated/apple/CSS/REACSSPlatformValue.h>
24+
#endif
25+
1526
namespace reanimated::css {
1627

1728
using namespace facebook;
1829
using namespace react;
1930

20-
// Fully-parsed property config handed to the platform-side apply callback.
31+
#ifndef __APPLE__
32+
using PlatformValue = std::monostate;
33+
#endif
34+
35+
using ParsedPlatformDiffs = std::unordered_map<std::string, std::pair<PlatformValue, PlatformValue>>;
36+
37+
/// A transition endpoint reduced to the shapes platform parsing recognizes,
38+
/// extracted from a jsi::Value or a folly::dynamic into one representation.
39+
/// monostate is a null or absent endpoint, resolved to the property's default.
40+
using CSSEndpointValue = std::variant<std::monostate, double, std::array<double, 2>>;
41+
2142
struct CSSPlatformTransitionPropertyConfig {
2243
Tag viewTag;
2344
std::string propertyName;
@@ -28,26 +49,30 @@ struct CSSPlatformTransitionPropertyConfig {
2849
EasingConfig easing;
2950
};
3051

31-
// Raw entry the proxy hands off to CSSPlatformTransition before any value
32-
// parsing happens. Carries the original jsi value pair + timing settings so
33-
// the platform side can convert lazily and inspect both from/to if needed.
34-
struct CSSPlatformTransitionRawEntry {
52+
struct CSSPlatformTransitionEntry {
3553
std::string propertyName;
36-
PropertyValueDiff valueDiff;
54+
PlatformValue fromValue;
55+
PlatformValue toValue;
3756
CSSTransitionPropertySettings settings;
3857
};
3958

4059
struct CSSPlatformTransitionConfig {
4160
PropertiesSettingsMap changedPropertiesSettings;
42-
std::vector<CSSPlatformTransitionRawEntry> changedProperties;
61+
std::vector<CSSPlatformTransitionEntry> changedProperties;
4362
std::vector<std::string> removedProperties;
4463

4564
bool empty() const {
4665
return changedPropertiesSettings.empty() && changedProperties.empty() && removedProperties.empty();
4766
}
4867
};
4968

69+
/// Whether the platform can animate the property natively for the given easing.
5070
using CSSCanRoutePropertyFunction = std::function<bool(const std::string &propertyName, const EasingConfig &easing)>;
71+
/// Parses an extracted endpoint into the platform's value representation. An
72+
/// empty endpoint resolves to the property's CSS default; nullopt means the
73+
/// value is not expressible natively and the property runs on the loop.
74+
using CSSParseValueFunction =
75+
std::function<std::optional<PlatformValue>(const std::string &propertyName, const CSSEndpointValue &value)>;
5176
using CSSApplyTransitionFunction = std::function<void(const CSSPlatformTransitionPropertyConfig &config)>;
5277
using CSSRemoveTransitionFunction = std::function<void(Tag viewTag, const std::string &propertyName)>;
5378

@@ -64,24 +89,39 @@ class CSSPlatformTransitionProxy {
6489
CSSTransitionRouting routing;
6590
};
6691

92+
struct ProcessedDynamicDiffs {
93+
PropertyValueDynamicDiffsMap loop;
94+
ParsedPlatformDiffs platform;
95+
};
96+
6797
CSSPlatformTransitionProxy(
6898
CSSCanRoutePropertyFunction canRoute,
99+
CSSParseValueFunction parseValue,
69100
CSSApplyTransitionFunction applyTransition,
70101
CSSRemoveTransitionFunction removeTransition);
71102

72103
void run(const CSSPlatformTransitionPropertyConfig &config) const;
73104
void remove(Tag viewTag, const std::string &propertyName) const;
74105

75-
// Filters the incoming config into loop / platform buckets and emits implicit
76-
// cancels on the old side when a property migrates compared to previousRouting.
77-
// The platform side does its own value parsing inside CSSPlatformTransition::run -
78-
// the proxy only forwards the raw jsi value pair via the raw entry.
79-
ProcessedConfig processConfig(CSSTransitionConfig &&config, const CSSTransitionRouting &previousRouting) const;
106+
/// Filters the config into loop / platform buckets, emitting implicit cancels
107+
/// on the old side when a property migrates vs previousRouting. A property
108+
/// routes to the platform only when canRoute holds and both endpoints parse.
109+
ProcessedConfig
110+
processConfig(jsi::Runtime &rt, CSSTransitionConfig &&config, const CSSTransitionRouting &previousRouting) const;
111+
112+
/// Splits already-routed pseudo-selector toggle diffs into loop / platform
113+
/// buckets, parsing the platform endpoints; values the platform cannot
114+
/// express fall back to the loop.
115+
ProcessedDynamicDiffs processDynamicDiffs(
116+
const CSSTransitionRouting &routing,
117+
const PropertyValueDynamicDiffsMap &propertyDiffs) const;
80118

81119
private:
82120
bool canRoute(const std::string &propertyName, const EasingConfig &easing) const;
121+
std::optional<PlatformValue> parseValue(const std::string &propertyName, const CSSEndpointValue &value) const;
83122

84123
CSSCanRoutePropertyFunction canRoute_;
124+
CSSParseValueFunction parseValue_;
85125
CSSApplyTransitionFunction applyTransition_;
86126
CSSRemoveTransitionFunction removeTransition_;
87127
};

0 commit comments

Comments
 (0)