react-native-smooth-clip-view 0.2.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -349
- package/android/build.gradle +8 -4
- package/android/proguard-rules.pro +3 -3
- package/android/src/main/cpp/SmoothClipAndroid.h +13 -6
- package/android/src/main/cpp/SmoothClipBindings.cpp +605 -123
- package/android/src/main/cpp/SmoothClipRegistry.cpp +975 -209
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +192 -118
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +22 -4
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +81 -56
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +449 -240
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +150 -38
- package/cpp/SmoothClipAnimationCurve.h +317 -397
- package/cpp/SmoothClipRegistry.h +84 -20
- package/cpp/SmoothClipSharedGeometry.h +131 -22
- package/cpp/SmoothClipVelocityTracker.h +16 -12
- package/ios/SmoothClipGeometry.h +188 -21
- package/ios/SmoothClipRegistry.mm +869 -368
- package/ios/SmoothClipRegistrySnapshot.h +13 -0
- package/ios/SmoothClipTurboModule.cpp +513 -156
- package/ios/SmoothClipTurboModule.h +123 -56
- package/ios/SmoothClipView.mm +1074 -449
- package/ios/SmoothClipViewRegistry.h +3 -7
- package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/lib/module/NativeSmoothClipModule.js.map +1 -1
- package/lib/module/SmoothClipView.ios.js +94 -11
- package/lib/module/SmoothClipView.ios.js.map +1 -1
- package/lib/module/SmoothClipView.js +1 -46
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +32 -12
- package/lib/module/capabilities.js +4 -0
- package/lib/module/capabilities.js.map +1 -0
- package/lib/module/capabilities.native.js +18 -0
- package/lib/module/capabilities.native.js.map +1 -0
- package/lib/module/capabilityTypes.js +2 -0
- package/lib/module/capabilityTypes.js.map +1 -0
- package/lib/module/controllerInternals.js +28 -0
- package/lib/module/controllerInternals.js.map +1 -0
- package/lib/module/controllerLifecycle.js +9 -0
- package/lib/module/controllerLifecycle.js.map +1 -0
- package/lib/module/controllerTypes.js +4 -0
- package/lib/module/controllerTypes.js.map +1 -0
- package/lib/module/controllers.android.js +4 -0
- package/lib/module/controllers.android.js.map +1 -0
- package/lib/module/controllers.ios.js +4 -0
- package/lib/module/controllers.ios.js.map +1 -0
- package/lib/module/controllers.js +4 -0
- package/lib/module/controllers.js.map +1 -0
- package/lib/module/controllers.native.js +89 -0
- package/lib/module/controllers.native.js.map +1 -0
- package/lib/module/geometry.js +180 -30
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groupTypes.js +4 -0
- package/lib/module/groupTypes.js.map +1 -0
- package/lib/module/groups.android.js +4 -0
- package/lib/module/groups.android.js.map +1 -0
- package/lib/module/groups.ios.js +4 -0
- package/lib/module/groups.ios.js.map +1 -0
- package/lib/module/groups.js +4 -0
- package/lib/module/groups.js.map +1 -0
- package/lib/module/groups.native.js +399 -0
- package/lib/module/groups.native.js.map +1 -0
- package/lib/module/ids.js +8 -0
- package/lib/module/ids.js.map +1 -0
- package/lib/module/index.js +4 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/nativeCompletion.js +37 -16
- package/lib/module/nativeCompletion.js.map +1 -1
- package/lib/module/presentationCodec.js +81 -0
- package/lib/module/presentationCodec.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +20 -4
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts +2 -10
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +139 -4
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +16 -3
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/capabilities.d.ts +3 -0
- package/lib/typescript/src/capabilities.d.ts.map +1 -0
- package/lib/typescript/src/capabilities.native.d.ts +3 -0
- package/lib/typescript/src/capabilities.native.d.ts.map +1 -0
- package/lib/typescript/src/capabilityTypes.d.ts +4 -0
- package/lib/typescript/src/capabilityTypes.d.ts.map +1 -0
- package/lib/typescript/src/controllerInternals.d.ts +14 -0
- package/lib/typescript/src/controllerInternals.d.ts.map +1 -0
- package/lib/typescript/src/controllerLifecycle.d.ts +3 -0
- package/lib/typescript/src/controllerLifecycle.d.ts.map +1 -0
- package/lib/typescript/src/controllerTypes.d.ts +62 -0
- package/lib/typescript/src/controllerTypes.d.ts.map +1 -0
- package/lib/typescript/src/controllers.android.d.ts +2 -0
- package/lib/typescript/src/controllers.android.d.ts.map +1 -0
- package/lib/typescript/src/controllers.d.ts +2 -0
- package/lib/typescript/src/controllers.d.ts.map +1 -0
- package/lib/typescript/src/controllers.ios.d.ts +2 -0
- package/lib/typescript/src/controllers.ios.d.ts.map +1 -0
- package/lib/typescript/src/controllers.native.d.ts +3 -0
- package/lib/typescript/src/controllers.native.d.ts.map +1 -0
- package/lib/typescript/src/geometry.d.ts +49 -5
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/groupTypes.d.ts +31 -0
- package/lib/typescript/src/groupTypes.d.ts.map +1 -0
- package/lib/typescript/src/groups.android.d.ts +2 -0
- package/lib/typescript/src/groups.android.d.ts.map +1 -0
- package/lib/typescript/src/groups.d.ts +2 -0
- package/lib/typescript/src/groups.d.ts.map +1 -0
- package/lib/typescript/src/groups.ios.d.ts +2 -0
- package/lib/typescript/src/groups.ios.d.ts.map +1 -0
- package/lib/typescript/src/groups.native.d.ts +3 -0
- package/lib/typescript/src/groups.native.d.ts.map +1 -0
- package/lib/typescript/src/ids.d.ts +2 -0
- package/lib/typescript/src/ids.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +8 -5
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/nativeCompletion.d.ts +16 -6
- package/lib/typescript/src/nativeCompletion.d.ts.map +1 -1
- package/lib/typescript/src/presentationCodec.d.ts +10 -0
- package/lib/typescript/src/presentationCodec.d.ts.map +1 -0
- package/package.json +17 -13
- package/src/NativeSmoothClipModule.ts +80 -56
- package/src/SmoothClipView.ios.tsx +142 -16
- package/src/SmoothClipView.tsx +6 -76
- package/src/SmoothClipViewNativeComponent.ts +32 -12
- package/src/capabilities.native.ts +19 -0
- package/src/capabilities.ts +2 -0
- package/src/capabilityTypes.ts +3 -0
- package/src/controllerInternals.ts +52 -0
- package/src/controllerLifecycle.ts +6 -0
- package/src/controllerTypes.ts +87 -0
- package/src/controllers.android.ts +1 -0
- package/src/controllers.ios.ts +1 -0
- package/src/controllers.native.ts +104 -0
- package/src/controllers.ts +1 -0
- package/src/geometry.ts +293 -45
- package/src/groupTypes.ts +53 -0
- package/src/groups.android.ts +1 -0
- package/src/groups.ios.ts +1 -0
- package/src/groups.native.ts +604 -0
- package/src/groups.ts +1 -0
- package/src/ids.ts +6 -0
- package/src/index.ts +32 -12
- package/src/nativeCompletion.ts +67 -32
- package/src/presentationCodec.ts +134 -0
- package/lib/module/SmoothClipView.web.js +0 -54
- package/lib/module/SmoothClipView.web.js.map +0 -1
- package/lib/module/driverState.js +0 -110
- package/lib/module/driverState.js.map +0 -1
- package/lib/module/driverTypes.js +0 -4
- package/lib/module/driverTypes.js.map +0 -1
- package/lib/module/drivers.android.js +0 -6
- package/lib/module/drivers.android.js.map +0 -1
- package/lib/module/drivers.ios.js +0 -6
- package/lib/module/drivers.ios.js.map +0 -1
- package/lib/module/drivers.js +0 -284
- package/lib/module/drivers.js.map +0 -1
- package/lib/module/drivers.native.js +0 -556
- package/lib/module/drivers.native.js.map +0 -1
- package/lib/module/index.web.js +0 -8
- package/lib/module/index.web.js.map +0 -1
- package/lib/module/reactRequests.js +0 -56
- package/lib/module/reactRequests.js.map +0 -1
- package/lib/module/webClipPath.js +0 -25
- package/lib/module/webClipPath.js.map +0 -1
- package/lib/typescript/src/SmoothClipView.web.d.ts +0 -10
- package/lib/typescript/src/SmoothClipView.web.d.ts.map +0 -1
- package/lib/typescript/src/driverState.d.ts +0 -31
- package/lib/typescript/src/driverState.d.ts.map +0 -1
- package/lib/typescript/src/driverTypes.d.ts +0 -107
- package/lib/typescript/src/driverTypes.d.ts.map +0 -1
- package/lib/typescript/src/drivers.android.d.ts +0 -3
- package/lib/typescript/src/drivers.android.d.ts.map +0 -1
- package/lib/typescript/src/drivers.d.ts +0 -5
- package/lib/typescript/src/drivers.d.ts.map +0 -1
- package/lib/typescript/src/drivers.ios.d.ts +0 -3
- package/lib/typescript/src/drivers.ios.d.ts.map +0 -1
- package/lib/typescript/src/drivers.native.d.ts +0 -5
- package/lib/typescript/src/drivers.native.d.ts.map +0 -1
- package/lib/typescript/src/index.web.d.ts +0 -9
- package/lib/typescript/src/index.web.d.ts.map +0 -1
- package/lib/typescript/src/reactRequests.d.ts +0 -9
- package/lib/typescript/src/reactRequests.d.ts.map +0 -1
- package/lib/typescript/src/webClipPath.d.ts +0 -7
- package/lib/typescript/src/webClipPath.d.ts.map +0 -1
- package/src/SmoothClipView.web.tsx +0 -66
- package/src/driverState.ts +0 -158
- package/src/driverTypes.ts +0 -140
- package/src/drivers.android.ts +0 -8
- package/src/drivers.ios.ts +0 -8
- package/src/drivers.native.ts +0 -899
- package/src/drivers.ts +0 -427
- package/src/index.web.ts +0 -23
- package/src/reactRequests.ts +0 -76
- package/src/webClipPath.ts +0 -30
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
#include <algorithm>
|
|
10
10
|
#include <array>
|
|
11
11
|
#include <atomic>
|
|
12
|
+
#include <cassert>
|
|
12
13
|
#include <chrono>
|
|
13
14
|
#include <cmath>
|
|
14
15
|
#include <limits>
|
|
@@ -26,8 +27,8 @@ using facebook::jni::alias_ref;
|
|
|
26
27
|
using facebook::jni::global_ref;
|
|
27
28
|
|
|
28
29
|
enum class Ownership { Interactive, Native };
|
|
29
|
-
enum class AnimationKind { Timing, Spring
|
|
30
|
-
enum class ViewParticipation { Deferred, Active
|
|
30
|
+
enum class AnimationKind { Timing, Spring };
|
|
31
|
+
enum class ViewParticipation { Deferred, Active };
|
|
31
32
|
|
|
32
33
|
// Load-bearing invariant, not just a convenience clock: advance()'s frame-clock
|
|
33
34
|
// anchor compares this value ABSOLUTELY against a choreographer frame stamp
|
|
@@ -51,40 +52,35 @@ double nowSeconds() {
|
|
|
51
52
|
|
|
52
53
|
struct ActiveAnimation {
|
|
53
54
|
int32_t id = 0;
|
|
55
|
+
int32_t completionTag = 0;
|
|
54
56
|
AnimationKind kind = AnimationKind::Timing;
|
|
55
57
|
Presentation start{{0, 0, 0, 0, 0}, 0, 0};
|
|
56
58
|
Presentation target{{0, 0, 0, 0, 0}, 0, 0};
|
|
57
59
|
Presentation current{{0, 0, 0, 0, 0}, 0, 0};
|
|
58
60
|
TimingAnimation timing{};
|
|
59
61
|
SpringAnimation spring{};
|
|
60
|
-
// Owns the keyframes and their precomputed monotone-cubic tangents; the
|
|
61
|
-
// tangent solve happens on reset(), never per frame.
|
|
62
|
-
KeyframeCurve keyframes;
|
|
63
62
|
double durationS = 0;
|
|
64
63
|
double startedAtS = 0;
|
|
65
|
-
// Integrated spring state
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
// Integrated spring state. Shadow channels are skipped when neither endpoint
|
|
65
|
+
// has a visible shadow.
|
|
66
|
+
Channels springPosition{};
|
|
67
|
+
Channels springVelocity{};
|
|
68
|
+
std::size_t springChannelCount = kBaseChannelCount;
|
|
68
69
|
double lastFrameS = 0;
|
|
69
|
-
|
|
70
|
-
// False while
|
|
71
|
-
//
|
|
72
|
-
// (pre-registration, or a detached/unsized host), or re-latched by
|
|
73
|
-
// unregisterViewAndroid when the last DISPLAYABLE host left mid-flight.
|
|
74
|
-
// Started by whichever lifecycle/geometry update first sees a displayable
|
|
75
|
-
// entry; each rebases the clock through startLatchedAnimation.
|
|
70
|
+
ScalarSpringState scalarSpring{0, 0};
|
|
71
|
+
// False only while a pre-ready animation waits for its first displayable
|
|
72
|
+
// host. Once started, host loss freezes the visible presentation.
|
|
76
73
|
bool started = false;
|
|
77
74
|
// False until the first advance() translates the wall-clock start stamp
|
|
78
75
|
// onto the choreographer frame-time axis (elapsed-preserving). Every
|
|
79
|
-
//
|
|
80
|
-
// a start that carried the JS-captured Reanimated stamp, which arrives
|
|
76
|
+
// A start that carried the JS-captured Reanimated stamp arrives
|
|
81
77
|
// pre-anchored (see startAnimation) and must not be min()'d again.
|
|
82
78
|
bool frameClockAnchored = false;
|
|
83
79
|
};
|
|
84
80
|
|
|
85
81
|
// Per-view fanout state. Density and host metrics are pushed from Kotlin at
|
|
86
|
-
// register time and on size/density changes; density 0
|
|
87
|
-
//
|
|
82
|
+
// register time and on size/density changes; density 0 falls back to DIP
|
|
83
|
+
// delivery until physical host metrics arrive.
|
|
88
84
|
struct ViewEntry {
|
|
89
85
|
global_ref<JSmoothClipView> view;
|
|
90
86
|
double density = 0;
|
|
@@ -104,6 +100,8 @@ struct DriverState {
|
|
|
104
100
|
// replay, hosts mounted in another subtree). The entry is erased when the
|
|
105
101
|
// last view leaves and revived by a take-ownership setPresentation.
|
|
106
102
|
bool destroyed = false;
|
|
103
|
+
// Non-zero while this driver is an immutable member of one active group.
|
|
104
|
+
int32_t groupId = 0;
|
|
107
105
|
|
|
108
106
|
// 'inherit' velocity samples; recording/coalescing/projection live in the
|
|
109
107
|
// shared cpp/SmoothClipVelocityTracker.h (behavior-paired with iOS).
|
|
@@ -115,6 +113,34 @@ struct CompletionSink {
|
|
|
115
113
|
CompletionCallback callback;
|
|
116
114
|
};
|
|
117
115
|
|
|
116
|
+
struct GroupCompletionSink {
|
|
117
|
+
const void *owner = nullptr;
|
|
118
|
+
GroupCompletionCallback callback;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
struct GroupMemberAnimation {
|
|
122
|
+
uint64_t driverId = 0;
|
|
123
|
+
ActiveAnimation animation;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
struct ActiveGroup {
|
|
127
|
+
uint64_t controllerId = 0;
|
|
128
|
+
int32_t id = 0;
|
|
129
|
+
int32_t completionTag = 0;
|
|
130
|
+
AnimationKind kind = AnimationKind::Timing;
|
|
131
|
+
TimingAnimation timing{};
|
|
132
|
+
SpringAnimation spring{};
|
|
133
|
+
ScalarSpringState scalarSpring{0, 0};
|
|
134
|
+
double durationS = 0;
|
|
135
|
+
double startedAtS = 0;
|
|
136
|
+
double lastFrameS = 0;
|
|
137
|
+
bool started = false;
|
|
138
|
+
bool frameClockAnchored = false;
|
|
139
|
+
std::vector<GroupMemberAnimation> members;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
bool anyDisplayableView(const DriverState &state);
|
|
143
|
+
|
|
118
144
|
std::unordered_map<uint64_t, DriverState> ®istry() {
|
|
119
145
|
static std::unordered_map<uint64_t, DriverState> value;
|
|
120
146
|
return value;
|
|
@@ -125,6 +151,21 @@ CompletionSink &completionSink() {
|
|
|
125
151
|
return sink;
|
|
126
152
|
}
|
|
127
153
|
|
|
154
|
+
GroupCompletionSink &groupCompletionSink() {
|
|
155
|
+
static GroupCompletionSink sink;
|
|
156
|
+
return sink;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
std::unordered_map<int32_t, ActiveGroup> &groupRegistry() {
|
|
160
|
+
static std::unordered_map<int32_t, ActiveGroup> value;
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
std::vector<int32_t> &animatingGroups() {
|
|
165
|
+
static std::vector<int32_t> value;
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
|
|
128
169
|
// The sink is written from the JS thread (installBindings / invalidate) and
|
|
129
170
|
// invoked on main. Invoking under the lock also keeps the owning bindings
|
|
130
171
|
// state alive for the duration of the callback.
|
|
@@ -213,9 +254,16 @@ void scheduleFrame() {
|
|
|
213
254
|
|
|
214
255
|
// --- Math (ported from SmoothClipRegistry.kt so both platforms match) -----
|
|
215
256
|
//
|
|
216
|
-
// clamp01, toChannels/fromChannels, interpolate, the frame-clock anchor
|
|
217
|
-
//
|
|
218
|
-
//
|
|
257
|
+
// clamp01, toChannels/fromChannels, interpolate, and the frame-clock anchor
|
|
258
|
+
// live in cpp/SmoothClipAnimationCurve.h so a test binary can reach them
|
|
259
|
+
// without linking fbjni; ios/tests pins their behavior.
|
|
260
|
+
|
|
261
|
+
std::array<double, 11> velocityChannels(const Presentation &presentation) {
|
|
262
|
+
const Channels channels = toChannels(presentation);
|
|
263
|
+
std::array<double, 11> result{};
|
|
264
|
+
std::copy_n(channels.begin(), result.size(), result.begin());
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
219
267
|
|
|
220
268
|
// Springs settle below these per-channel thresholds (DIP and DIP/s).
|
|
221
269
|
constexpr double kSpringSettleDisplacement = 0.05;
|
|
@@ -230,7 +278,9 @@ constexpr double kSpringMaxDurationS = 10.0;
|
|
|
230
278
|
// stiffness/mass = 200k. Termination is settle-based: every channel must be
|
|
231
279
|
// near its target and nearly stationary at the same time.
|
|
232
280
|
bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
233
|
-
const auto
|
|
281
|
+
const auto normalizedEndpoints = normalizeShadowEndpoints(
|
|
282
|
+
animation.start, animation.target);
|
|
283
|
+
const auto target = toChannels(normalizedEndpoints.second);
|
|
234
284
|
double dt = now - animation.lastFrameS;
|
|
235
285
|
animation.lastFrameS = now;
|
|
236
286
|
if (dt < 0) dt = 0;
|
|
@@ -242,7 +292,7 @@ bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
|
242
292
|
while (dt > 0) {
|
|
243
293
|
const double step = std::min(dt, kMaxStepS);
|
|
244
294
|
dt -= step;
|
|
245
|
-
for (
|
|
295
|
+
for (std::size_t index = 0; index < animation.springChannelCount; index += 1) {
|
|
246
296
|
const double displacement =
|
|
247
297
|
animation.springPosition[index] - target[index];
|
|
248
298
|
const double acceleration =
|
|
@@ -254,7 +304,7 @@ bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
|
254
304
|
}
|
|
255
305
|
}
|
|
256
306
|
bool settled = true;
|
|
257
|
-
for (
|
|
307
|
+
for (std::size_t index = 0; index < animation.springChannelCount; index += 1) {
|
|
258
308
|
if (!std::isfinite(animation.springPosition[index]) ||
|
|
259
309
|
!std::isfinite(animation.springVelocity[index])) {
|
|
260
310
|
// A divergent configuration must never freeze the clip: end at target.
|
|
@@ -269,7 +319,14 @@ bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
|
269
319
|
settled = false;
|
|
270
320
|
}
|
|
271
321
|
}
|
|
272
|
-
animation.current = fromChannels(
|
|
322
|
+
animation.current = fromChannels(
|
|
323
|
+
animation.springPosition,
|
|
324
|
+
animation.start.clip.curve,
|
|
325
|
+
animation.start.shadow.enabled || animation.target.shadow.enabled);
|
|
326
|
+
if (!canonicalizePresentation(animation.current)) {
|
|
327
|
+
animation.current = animation.target;
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
273
330
|
return settled || now - animation.startedAtS >= kSpringMaxDurationS;
|
|
274
331
|
}
|
|
275
332
|
|
|
@@ -289,39 +346,109 @@ bool shouldReduceMotion(int32_t setting) {
|
|
|
289
346
|
|
|
290
347
|
// --- State helpers --------------------------------------------------------
|
|
291
348
|
|
|
292
|
-
void emitCompletion(
|
|
349
|
+
void emitCompletion(
|
|
350
|
+
uint64_t driverId,
|
|
351
|
+
int32_t animationId,
|
|
352
|
+
int32_t completionTag,
|
|
353
|
+
bool finished) {
|
|
293
354
|
std::lock_guard<std::mutex> lock(completionSinkMutex());
|
|
294
355
|
if (completionSink().callback) {
|
|
295
|
-
completionSink().callback(
|
|
356
|
+
completionSink().callback(
|
|
357
|
+
driverId, animationId, completionTag, finished);
|
|
296
358
|
}
|
|
297
359
|
}
|
|
298
360
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
361
|
+
void emitGroupCompletion(const ActiveGroup &group, bool finished) {
|
|
362
|
+
std::vector<DriverSnapshot> snapshots;
|
|
363
|
+
snapshots.reserve(group.members.size());
|
|
364
|
+
for (const GroupMemberAnimation &member : group.members) {
|
|
365
|
+
const auto iterator = registry().find(member.driverId);
|
|
366
|
+
if (iterator == registry().end() || iterator->second.destroyed) {
|
|
367
|
+
snapshots.push_back(
|
|
368
|
+
{member.driverId, unavailablePresentation(), false});
|
|
369
|
+
} else {
|
|
370
|
+
snapshots.push_back({
|
|
371
|
+
member.driverId,
|
|
372
|
+
iterator->second.latest,
|
|
373
|
+
anyDisplayableView(iterator->second),
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
std::lock_guard<std::mutex> lock(completionSinkMutex());
|
|
378
|
+
if (groupCompletionSink().callback) {
|
|
379
|
+
groupCompletionSink().callback(
|
|
380
|
+
group.controllerId,
|
|
381
|
+
group.id,
|
|
382
|
+
group.completionTag,
|
|
383
|
+
finished,
|
|
384
|
+
std::move(snapshots));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const GroupMemberAnimation *findGroupMember(
|
|
389
|
+
int32_t groupId,
|
|
390
|
+
uint64_t driverId) {
|
|
391
|
+
const auto groupIterator = groupRegistry().find(groupId);
|
|
392
|
+
if (groupIterator == groupRegistry().end()) return nullptr;
|
|
393
|
+
for (const GroupMemberAnimation &member : groupIterator->second.members) {
|
|
394
|
+
if (member.driverId == driverId) return &member;
|
|
395
|
+
}
|
|
396
|
+
return nullptr;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
Presentation visiblePresentation(
|
|
400
|
+
uint64_t driverId,
|
|
401
|
+
const DriverState &state) {
|
|
402
|
+
if (state.groupId != 0) {
|
|
403
|
+
const GroupMemberAnimation *member =
|
|
404
|
+
findGroupMember(state.groupId, driverId);
|
|
405
|
+
if (member != nullptr) return member->animation.current;
|
|
406
|
+
}
|
|
407
|
+
return state.animation.has_value() ? state.animation->current : state.latest;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Terminal per-frame delivery: scale canonical DIP geometry to pixels without
|
|
411
|
+
// consulting host metrics. Each SmoothClipView applies the fixed viewport crop.
|
|
302
412
|
void deliverToView(const ViewEntry &entry, const Presentation &presentation) {
|
|
303
413
|
if (entry.density <= 0) {
|
|
304
414
|
entry.view->applyClip(presentation);
|
|
305
415
|
return;
|
|
306
416
|
}
|
|
307
417
|
const double density = entry.density;
|
|
308
|
-
|
|
309
|
-
if (!
|
|
418
|
+
CanonicalClip clip;
|
|
419
|
+
if (!SmoothClipCanonicalize(
|
|
310
420
|
presentation.clip.x * density,
|
|
311
421
|
presentation.clip.y * density,
|
|
312
422
|
presentation.clip.width * density,
|
|
313
423
|
presentation.clip.height * density,
|
|
314
424
|
presentation.clip.radius * density,
|
|
315
|
-
|
|
316
|
-
|
|
425
|
+
presentation.clip.topLeftRadius * density,
|
|
426
|
+
presentation.clip.topRightRadius * density,
|
|
427
|
+
presentation.clip.bottomRightRadius * density,
|
|
428
|
+
presentation.clip.bottomLeftRadius * density,
|
|
429
|
+
presentation.clip.curve,
|
|
317
430
|
clip)) {
|
|
318
431
|
return;
|
|
319
432
|
}
|
|
320
433
|
const double translateX = presentation.contentTranslateX * density;
|
|
321
434
|
const double translateY = presentation.contentTranslateY * density;
|
|
322
435
|
// Atomic reject, mirroring the Kotlin DIP path's all-or-nothing gate.
|
|
323
|
-
if (!std::isfinite(translateX) || !std::isfinite(translateY)
|
|
324
|
-
|
|
436
|
+
if (!std::isfinite(translateX) || !std::isfinite(translateY) ||
|
|
437
|
+
!std::isfinite(presentation.contentScale) ||
|
|
438
|
+
presentation.contentScale <= 0) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
Shadow shadowPx = presentation.shadow;
|
|
442
|
+
shadowPx.offsetX *= density;
|
|
443
|
+
shadowPx.offsetY *= density;
|
|
444
|
+
shadowPx.blurRadius *= density;
|
|
445
|
+
shadowPx.spreadDistance *= density;
|
|
446
|
+
entry.view->applyClipPx(
|
|
447
|
+
clip,
|
|
448
|
+
translateX,
|
|
449
|
+
translateY,
|
|
450
|
+
presentation.contentScale,
|
|
451
|
+
shadowPx);
|
|
325
452
|
}
|
|
326
453
|
|
|
327
454
|
void applyToViews(DriverState &state, const Presentation &presentation) {
|
|
@@ -330,6 +457,22 @@ void applyToViews(DriverState &state, const Presentation &presentation) {
|
|
|
330
457
|
}
|
|
331
458
|
}
|
|
332
459
|
|
|
460
|
+
void setAutonomousMotion(DriverState &state, bool active) {
|
|
461
|
+
for (const ViewEntry &entry : state.views) {
|
|
462
|
+
entry.view->setAutonomousMotion(active);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
void applyAnimationFrameToViews(
|
|
467
|
+
DriverState &state,
|
|
468
|
+
const Presentation &presentation) {
|
|
469
|
+
for (const auto &entry : state.views) {
|
|
470
|
+
if (entry.participation == ViewParticipation::Active) {
|
|
471
|
+
deliverToView(entry, presentation);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
333
476
|
void applyPresentation(DriverState &state, const Presentation &presentation) {
|
|
334
477
|
state.latest = presentation;
|
|
335
478
|
state.hasLatest = true;
|
|
@@ -346,6 +489,7 @@ std::vector<uint64_t> &animatingDrivers() {
|
|
|
346
489
|
// visits animating drivers. Removal is eager: a stale id surviving a driver
|
|
347
490
|
// erase/recreate cycle would let one frame integrate the animation twice.
|
|
348
491
|
void clearActiveAnimation(uint64_t driverId, DriverState &state) {
|
|
492
|
+
setAutonomousMotion(state, false);
|
|
349
493
|
state.animation.reset();
|
|
350
494
|
for (ViewEntry &entry : state.views) {
|
|
351
495
|
entry.participation = ViewParticipation::Deferred;
|
|
@@ -358,8 +502,9 @@ void clearActiveAnimation(uint64_t driverId, DriverState &state) {
|
|
|
358
502
|
void finishActive(uint64_t driverId, DriverState &state, bool finished) {
|
|
359
503
|
if (!state.animation.has_value()) return;
|
|
360
504
|
const int32_t animationId = state.animation->id;
|
|
505
|
+
const int32_t completionTag = state.animation->completionTag;
|
|
361
506
|
clearActiveAnimation(driverId, state);
|
|
362
|
-
emitCompletion(driverId, animationId, finished);
|
|
507
|
+
emitCompletion(driverId, animationId, completionTag, finished);
|
|
363
508
|
}
|
|
364
509
|
|
|
365
510
|
Presentation prepareAnimation(
|
|
@@ -369,8 +514,10 @@ Presentation prepareAnimation(
|
|
|
369
514
|
Presentation target) {
|
|
370
515
|
const Presentation visibleBefore =
|
|
371
516
|
state.animation.has_value() ? state.animation->current : state.latest;
|
|
372
|
-
|
|
373
|
-
|
|
517
|
+
// `from` is an explicit part of the replacement transaction and therefore
|
|
518
|
+
// overrides the sampled native frame even when the previous owner was a
|
|
519
|
+
// native animation/group.
|
|
520
|
+
const bool acceptsInteractiveStart = start.hasInteractiveStart;
|
|
374
521
|
finishActive(driverId, state, false);
|
|
375
522
|
const Presentation resolvedStart =
|
|
376
523
|
acceptsInteractiveStart ? start.interactiveStart : visibleBefore;
|
|
@@ -399,16 +546,126 @@ bool anyDisplayableView(const DriverState &state) {
|
|
|
399
546
|
return false;
|
|
400
547
|
}
|
|
401
548
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
549
|
+
bool allGroupMembersReady(const ActiveGroup &group) {
|
|
550
|
+
for (const GroupMemberAnimation &member : group.members) {
|
|
551
|
+
const auto iterator = registry().find(member.driverId);
|
|
552
|
+
if (iterator == registry().end() || iterator->second.destroyed ||
|
|
553
|
+
!anyDisplayableView(iterator->second)) {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
void removeAnimatingGroup(int32_t groupId) {
|
|
561
|
+
auto &active = animatingGroups();
|
|
562
|
+
active.erase(std::remove(active.begin(), active.end(), groupId), active.end());
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
void applyGroupCurrent(const ActiveGroup &group) {
|
|
566
|
+
// Group readiness is the participation contract. Each controller owns one
|
|
567
|
+
// host, and host loss removes the group before another frame can advance.
|
|
568
|
+
for (const GroupMemberAnimation &member : group.members) {
|
|
569
|
+
const auto iterator = registry().find(member.driverId);
|
|
570
|
+
if (iterator != registry().end()) {
|
|
571
|
+
applyToViews(iterator->second, member.animation.current);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
void startGroupFrameLoop(ActiveGroup &group) {
|
|
577
|
+
assert(group.started);
|
|
578
|
+
assert(allGroupMembersReady(group));
|
|
579
|
+
for (const GroupMemberAnimation &member : group.members) {
|
|
580
|
+
auto iterator = registry().find(member.driverId);
|
|
581
|
+
if (iterator != registry().end()) {
|
|
582
|
+
setAutonomousMotion(iterator->second, true);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
auto &active = animatingGroups();
|
|
586
|
+
if (std::find(active.begin(), active.end(), group.id) == active.end()) {
|
|
587
|
+
active.push_back(group.id);
|
|
588
|
+
}
|
|
589
|
+
applyGroupCurrent(group);
|
|
590
|
+
scheduleFrame();
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
std::vector<DriverSnapshot> finishGroupImpl(
|
|
594
|
+
int32_t groupId,
|
|
595
|
+
bool useTarget,
|
|
596
|
+
bool finished) {
|
|
597
|
+
auto groupIterator = groupRegistry().find(groupId);
|
|
598
|
+
if (groupIterator == groupRegistry().end()) return {};
|
|
599
|
+
ActiveGroup group = std::move(groupIterator->second);
|
|
600
|
+
groupRegistry().erase(groupIterator);
|
|
601
|
+
removeAnimatingGroup(groupId);
|
|
602
|
+
|
|
603
|
+
std::vector<DriverSnapshot> snapshots;
|
|
604
|
+
snapshots.reserve(group.members.size());
|
|
605
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
606
|
+
auto stateIterator = registry().find(member.driverId);
|
|
607
|
+
if (stateIterator == registry().end()) {
|
|
608
|
+
snapshots.push_back({member.driverId, unavailablePresentation(), false});
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
DriverState &state = stateIterator->second;
|
|
612
|
+
const Presentation result =
|
|
613
|
+
useTarget ? member.animation.target : member.animation.current;
|
|
614
|
+
state.groupId = 0;
|
|
615
|
+
state.latest = result;
|
|
616
|
+
state.hasLatest = true;
|
|
617
|
+
state.ownership = Ownership::Interactive;
|
|
618
|
+
setAutonomousMotion(state, false);
|
|
619
|
+
applyToViews(state, result);
|
|
620
|
+
snapshots.push_back(
|
|
621
|
+
{member.driverId, result, !state.destroyed && anyDisplayableView(state)});
|
|
622
|
+
}
|
|
623
|
+
emitGroupCompletion(group, finished);
|
|
624
|
+
return snapshots;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
void startPendingGroup(ActiveGroup &group) {
|
|
628
|
+
const double now = nowSeconds();
|
|
629
|
+
group.started = true;
|
|
630
|
+
group.startedAtS = now;
|
|
631
|
+
group.lastFrameS = now;
|
|
632
|
+
group.frameClockAnchored = false;
|
|
633
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
634
|
+
member.animation.startedAtS = now;
|
|
635
|
+
member.animation.lastFrameS = now;
|
|
636
|
+
member.animation.frameClockAnchored = false;
|
|
637
|
+
}
|
|
638
|
+
startGroupFrameLoop(group);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
void reconcileGroupReadiness(uint64_t driverId) {
|
|
642
|
+
const auto stateIterator = registry().find(driverId);
|
|
643
|
+
if (stateIterator == registry().end() || stateIterator->second.groupId == 0) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const int32_t groupId = stateIterator->second.groupId;
|
|
647
|
+
auto groupIterator = groupRegistry().find(groupId);
|
|
648
|
+
if (groupIterator == groupRegistry().end()) {
|
|
649
|
+
stateIterator->second.groupId = 0;
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
ActiveGroup &group = groupIterator->second;
|
|
653
|
+
const bool ready = allGroupMembersReady(group);
|
|
654
|
+
if (!group.started) {
|
|
655
|
+
if (ready) startPendingGroup(group);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
if (ready) return;
|
|
659
|
+
finishGroupImpl(groupId, false, false);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Starts a pre-ready animation with its full duration, then joins the
|
|
663
|
+
// choreographer loop.
|
|
664
|
+
void startPendingAnimation(uint64_t driverId, DriverState &state) {
|
|
405
665
|
auto &animation = *state.animation;
|
|
406
666
|
animation.started = true;
|
|
407
667
|
animation.startedAtS = nowSeconds();
|
|
408
668
|
animation.lastFrameS = animation.startedAtS;
|
|
409
|
-
// Load-bearing for re-latch resumes: unregisterViewAndroid rewrites this
|
|
410
|
-
// ActiveAnimation in place, so a stale anchor would replay the fraction-0
|
|
411
|
-
// first frame the anchor exists to remove.
|
|
412
669
|
animation.frameClockAnchored = false;
|
|
413
670
|
auto &active = animatingDrivers();
|
|
414
671
|
if (std::find(active.begin(), active.end(), driverId) == active.end()) {
|
|
@@ -419,6 +676,7 @@ void startLatchedAnimation(uint64_t driverId, DriverState &state) {
|
|
|
419
676
|
entry.participation = ViewParticipation::Active;
|
|
420
677
|
}
|
|
421
678
|
}
|
|
679
|
+
setAutonomousMotion(state, true);
|
|
422
680
|
applyToViews(state, animation.start);
|
|
423
681
|
scheduleFrame();
|
|
424
682
|
}
|
|
@@ -434,16 +692,16 @@ int32_t startAnimation(
|
|
|
434
692
|
// animation arrives pre-anchored and advance() must not min() it against
|
|
435
693
|
// the dispatching frame, which for a CALLBACK_INPUT start is EARLIER than
|
|
436
694
|
// the call and would re-open the intra-frame lead the hint closes. Without
|
|
437
|
-
// a hint (NaN:
|
|
695
|
+
// a hint (NaN: unstamped callers, tests, iOS ignoring the field)
|
|
438
696
|
// this reduces exactly to the old nowSeconds() + min() anchor path.
|
|
439
697
|
const double wallNow = nowSeconds();
|
|
440
698
|
const StartStamp stamp = resolveStartStamp(startedAtHintS, wallNow);
|
|
441
699
|
animation.startedAtS = stamp.startedAtS;
|
|
442
700
|
animation.lastFrameS = stamp.startedAtS;
|
|
443
701
|
animation.frameClockAnchored = stamp.frameClockAnchored;
|
|
444
|
-
// current = start
|
|
702
|
+
// current = start while pre-ready is load-bearing: cancelAnimation,
|
|
445
703
|
// beginInteraction, prepareAnimation's visibleBefore and registerView's
|
|
446
|
-
// visible all read animation->current, giving a never-rendered
|
|
704
|
+
// visible all read animation->current, giving a never-rendered pending-run
|
|
447
705
|
// freeze-at-start / replace-from-start semantics with no extra branches.
|
|
448
706
|
animation.current = animation.start;
|
|
449
707
|
animation.started = anyDisplayableView(state);
|
|
@@ -454,9 +712,9 @@ int32_t startAnimation(
|
|
|
454
712
|
: ViewParticipation::Deferred;
|
|
455
713
|
}
|
|
456
714
|
if (!state.animation->started) {
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
715
|
+
// No host can display yet (animateTo raced the mount, or the host is
|
|
716
|
+
// detached/unsized). The first displayable registration, host-geometry
|
|
717
|
+
// push, or window attach starts the full clock, joins
|
|
460
718
|
// animatingDrivers() and schedules the frame loop. Non-zero id is still
|
|
461
719
|
// returned so the JS side does not treat this as rejection.
|
|
462
720
|
return state.animation->id;
|
|
@@ -465,60 +723,23 @@ int32_t startAnimation(
|
|
|
465
723
|
if (std::find(active.begin(), active.end(), driverId) == active.end()) {
|
|
466
724
|
active.push_back(driverId);
|
|
467
725
|
}
|
|
726
|
+
setAutonomousMotion(state, true);
|
|
468
727
|
applyToViews(state, state.animation->start);
|
|
469
728
|
scheduleFrame();
|
|
470
729
|
return state.animation->id;
|
|
471
730
|
}
|
|
472
731
|
|
|
473
|
-
void
|
|
732
|
+
void finishIfHostUnavailable(uint64_t driverId, DriverState &state) {
|
|
474
733
|
if (!state.animation.has_value() || !state.animation->started ||
|
|
475
734
|
anyDisplayableView(state)) {
|
|
476
735
|
return;
|
|
477
736
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
if (animation.kind == AnimationKind::Timing) {
|
|
486
|
-
const TimingContinuation continuation = timingContinuationAtFrame(
|
|
487
|
-
animation.timing,
|
|
488
|
-
animation.start,
|
|
489
|
-
animation.target,
|
|
490
|
-
animation.lastFrameS,
|
|
491
|
-
animation.startedAtS,
|
|
492
|
-
animation.durationS);
|
|
493
|
-
animation.current = continuation.start;
|
|
494
|
-
animation.timing = continuation.animation;
|
|
495
|
-
animation.durationS = continuation.animation.durationMs / 1000.0;
|
|
496
|
-
} else if (animation.kind == AnimationKind::Keyframes) {
|
|
497
|
-
const KeyframeContinuation continuation = keyframeContinuationAtFrame(
|
|
498
|
-
animation.keyframes.frames(),
|
|
499
|
-
animation.current,
|
|
500
|
-
animation.target,
|
|
501
|
-
animation.lastFrameS,
|
|
502
|
-
animation.startedAtS,
|
|
503
|
-
animation.durationS);
|
|
504
|
-
animation.current = continuation.start;
|
|
505
|
-
animation.keyframes.reset(continuation.frames);
|
|
506
|
-
animation.durationS = continuation.durationMs / 1000.0;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
if (state.destroyed ||
|
|
510
|
-
(animation.kind != AnimationKind::Spring && animation.durationS <= 0)) {
|
|
511
|
-
state.ownership = Ownership::Interactive;
|
|
512
|
-
finishActive(driverId, state, false);
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
animation.start = animation.current;
|
|
517
|
-
animation.started = false;
|
|
518
|
-
animation.frameClockAnchored = false;
|
|
519
|
-
auto &active = animatingDrivers();
|
|
520
|
-
active.erase(
|
|
521
|
-
std::remove(active.begin(), active.end(), driverId), active.end());
|
|
737
|
+
const Presentation target = state.animation->target;
|
|
738
|
+
state.latest = target;
|
|
739
|
+
state.hasLatest = true;
|
|
740
|
+
state.ownership = Ownership::Interactive;
|
|
741
|
+
applyToViews(state, target);
|
|
742
|
+
finishActive(driverId, state, true);
|
|
522
743
|
}
|
|
523
744
|
|
|
524
745
|
void reconcileViewDisplayability(
|
|
@@ -527,7 +748,7 @@ void reconcileViewDisplayability(
|
|
|
527
748
|
ViewEntry &entry) {
|
|
528
749
|
if (!state.animation.has_value()) return;
|
|
529
750
|
if (!state.animation->started) {
|
|
530
|
-
if (entryDisplayable(entry))
|
|
751
|
+
if (entryDisplayable(entry)) startPendingAnimation(driverId, state);
|
|
531
752
|
return;
|
|
532
753
|
}
|
|
533
754
|
|
|
@@ -537,20 +758,20 @@ void reconcileViewDisplayability(
|
|
|
537
758
|
deliverToView(entry, state.animation->current);
|
|
538
759
|
}
|
|
539
760
|
} else if (entry.participation == ViewParticipation::Active) {
|
|
540
|
-
entry.participation = ViewParticipation::
|
|
761
|
+
entry.participation = ViewParticipation::Deferred;
|
|
541
762
|
}
|
|
542
|
-
|
|
763
|
+
finishIfHostUnavailable(driverId, state);
|
|
543
764
|
}
|
|
544
765
|
|
|
545
766
|
void advance(uint64_t driverId, DriverState &state, double now) {
|
|
546
767
|
ActiveAnimation &animation = *state.animation;
|
|
547
768
|
|
|
548
769
|
if (!animation.frameClockAnchored) {
|
|
549
|
-
// This is the
|
|
770
|
+
// This is the unstamped/pre-ready fallback. Worklet-issued animations carry
|
|
550
771
|
// Reanimated's exact `__frameTimestamp || _getAnimationTimestamp()` value
|
|
551
772
|
// and arrive pre-anchored, so they skip this approximation — including the
|
|
552
773
|
// CALLBACK_INPUT case where the current frame stamp predates the call.
|
|
553
|
-
// Here startedAtS/lastFrameS hold nowSeconds() sampled at native
|
|
774
|
+
// Here startedAtS/lastFrameS hold nowSeconds() sampled at native
|
|
554
775
|
// attach or by an older caller, while `now` is the frame's vsync stamp on
|
|
555
776
|
// the same CLOCK_MONOTONIC timebase. Taking the earlier stamp prevents a
|
|
556
777
|
// mid-frame native start from duplicating fraction zero.
|
|
@@ -574,44 +795,97 @@ void advance(uint64_t driverId, DriverState &state, double now) {
|
|
|
574
795
|
if (animation.kind == AnimationKind::Spring) {
|
|
575
796
|
done = advanceSpring(animation, now);
|
|
576
797
|
} else {
|
|
577
|
-
// This is the frame whose presentation is delivered below. Lifecycle
|
|
578
|
-
// re-latch must trim from this timestamp, never from a later wall sample.
|
|
579
798
|
animation.lastFrameS = now;
|
|
580
799
|
const double fraction =
|
|
581
800
|
timingFraction(now, animation.startedAtS, animation.durationS);
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
animation.current =
|
|
801
|
+
const double eased = cubicBezier(
|
|
802
|
+
animation.timing.controlPoint1X,
|
|
803
|
+
animation.timing.controlPoint1Y,
|
|
804
|
+
animation.timing.controlPoint2X,
|
|
805
|
+
animation.timing.controlPoint2Y,
|
|
806
|
+
fraction);
|
|
807
|
+
animation.current = interpolate(animation.start, animation.target, eased);
|
|
808
|
+
const bool canonical = canonicalizePresentation(animation.current);
|
|
809
|
+
if (!canonical) {
|
|
810
|
+
animation.current = animation.target;
|
|
592
811
|
}
|
|
593
|
-
done = fraction >= 1.0;
|
|
812
|
+
done = !canonical || fraction >= 1.0;
|
|
594
813
|
}
|
|
595
814
|
// The completion branch below fans out the exact target; applying the
|
|
596
815
|
// integrated value too would double every JNI crossing on the final frame.
|
|
597
|
-
if (!done)
|
|
816
|
+
if (!done) applyAnimationFrameToViews(state, animation.current);
|
|
598
817
|
|
|
599
818
|
if (done) {
|
|
600
819
|
const int32_t animationId = animation.id;
|
|
601
|
-
|
|
602
|
-
for (const ViewEntry &entry : state.views) {
|
|
603
|
-
if (entry.participation == ViewParticipation::Suspended) {
|
|
604
|
-
finished = false;
|
|
605
|
-
break;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
820
|
+
const int32_t completionTag = animation.completionTag;
|
|
608
821
|
const Presentation target = animation.target;
|
|
609
822
|
clearActiveAnimation(driverId, state);
|
|
610
823
|
state.latest = target;
|
|
611
824
|
state.hasLatest = true;
|
|
612
825
|
state.ownership = Ownership::Interactive;
|
|
613
826
|
applyToViews(state, target);
|
|
614
|
-
emitCompletion(driverId, animationId,
|
|
827
|
+
emitCompletion(driverId, animationId, completionTag, true);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
void advanceGroup(int32_t groupId, ActiveGroup &group, double now) {
|
|
832
|
+
if (!group.frameClockAnchored) {
|
|
833
|
+
group.startedAtS = anchorStartTime(group.startedAtS, now);
|
|
834
|
+
group.lastFrameS = group.startedAtS;
|
|
835
|
+
group.frameClockAnchored = true;
|
|
836
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
837
|
+
member.animation.startedAtS = group.startedAtS;
|
|
838
|
+
member.animation.lastFrameS = group.startedAtS;
|
|
839
|
+
member.animation.frameClockAnchored = true;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
bool done = false;
|
|
844
|
+
if (group.kind == AnimationKind::Spring) {
|
|
845
|
+
const double deltaTime = now - group.lastFrameS;
|
|
846
|
+
group.lastFrameS = now;
|
|
847
|
+
group.scalarSpring = advanceScalarSpring(
|
|
848
|
+
group.scalarSpring, group.spring, deltaTime);
|
|
849
|
+
done = now - group.startedAtS >= kSpringMaxDurationS ||
|
|
850
|
+
relativeSpringEnergy(group.scalarSpring, group.spring) <=
|
|
851
|
+
group.spring.energyThreshold;
|
|
852
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
853
|
+
member.animation.current = interpolate(
|
|
854
|
+
member.animation.start,
|
|
855
|
+
member.animation.target,
|
|
856
|
+
done ? 1.0 : group.scalarSpring.position);
|
|
857
|
+
if (!canonicalizePresentation(member.animation.current)) {
|
|
858
|
+
member.animation.current = member.animation.target;
|
|
859
|
+
done = true;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
} else {
|
|
863
|
+
group.lastFrameS = now;
|
|
864
|
+
const double fraction =
|
|
865
|
+
timingFraction(now, group.startedAtS, group.durationS);
|
|
866
|
+
const double eased = cubicBezier(
|
|
867
|
+
group.timing.controlPoint1X,
|
|
868
|
+
group.timing.controlPoint1Y,
|
|
869
|
+
group.timing.controlPoint2X,
|
|
870
|
+
group.timing.controlPoint2Y,
|
|
871
|
+
fraction);
|
|
872
|
+
bool canonical = true;
|
|
873
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
874
|
+
member.animation.lastFrameS = now;
|
|
875
|
+
member.animation.current = interpolate(
|
|
876
|
+
member.animation.start, member.animation.target, eased);
|
|
877
|
+
if (!canonicalizePresentation(member.animation.current)) {
|
|
878
|
+
member.animation.current = member.animation.target;
|
|
879
|
+
canonical = false;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
done = !canonical || fraction >= 1.0;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (done) {
|
|
886
|
+
finishGroupImpl(groupId, true, true);
|
|
887
|
+
} else {
|
|
888
|
+
applyGroupCurrent(group);
|
|
615
889
|
}
|
|
616
890
|
}
|
|
617
891
|
|
|
@@ -634,7 +908,14 @@ void onFrameImpl(double now) {
|
|
|
634
908
|
}
|
|
635
909
|
advance(driverId, iterator->second, now);
|
|
636
910
|
}
|
|
637
|
-
|
|
911
|
+
static std::vector<int32_t> groupScratch;
|
|
912
|
+
groupScratch = animatingGroups();
|
|
913
|
+
for (const int32_t groupId : groupScratch) {
|
|
914
|
+
auto iterator = groupRegistry().find(groupId);
|
|
915
|
+
if (iterator == groupRegistry().end() || !iterator->second.started) continue;
|
|
916
|
+
advanceGroup(groupId, iterator->second, now);
|
|
917
|
+
}
|
|
918
|
+
if (!animatingDrivers().empty() || !animatingGroups().empty()) scheduleFrame();
|
|
638
919
|
}
|
|
639
920
|
|
|
640
921
|
} // namespace
|
|
@@ -673,6 +954,353 @@ void clearCompletionCallback(const void *owner) {
|
|
|
673
954
|
completionSink().callback = nullptr;
|
|
674
955
|
}
|
|
675
956
|
|
|
957
|
+
void setGroupCompletionCallback(
|
|
958
|
+
const void *owner,
|
|
959
|
+
GroupCompletionCallback callback) {
|
|
960
|
+
std::lock_guard<std::mutex> lock(completionSinkMutex());
|
|
961
|
+
groupCompletionSink().owner = owner;
|
|
962
|
+
groupCompletionSink().callback = std::move(callback);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
void clearGroupCompletionCallback(const void *owner) {
|
|
966
|
+
std::lock_guard<std::mutex> lock(completionSinkMutex());
|
|
967
|
+
if (groupCompletionSink().owner != owner) return;
|
|
968
|
+
groupCompletionSink().owner = nullptr;
|
|
969
|
+
groupCompletionSink().callback = nullptr;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
Presentation snapshotCurrent(uint64_t driverId) {
|
|
973
|
+
if (!isOnMainThread()) return unavailablePresentation();
|
|
974
|
+
const auto iterator = registry().find(driverId);
|
|
975
|
+
if (iterator == registry().end() || iterator->second.destroyed ||
|
|
976
|
+
!iterator->second.hasLatest) {
|
|
977
|
+
return unavailablePresentation();
|
|
978
|
+
}
|
|
979
|
+
return visiblePresentation(driverId, iterator->second);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
Presentation snapshotCurrentAndroid(uint64_t driverId) {
|
|
983
|
+
return snapshotCurrent(driverId);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
std::vector<DriverSnapshot> snapshotGroup(
|
|
987
|
+
const std::vector<uint64_t> &driverIds) {
|
|
988
|
+
std::vector<DriverSnapshot> snapshots;
|
|
989
|
+
snapshots.reserve(driverIds.size());
|
|
990
|
+
if (!isOnMainThread()) return snapshots;
|
|
991
|
+
for (const uint64_t driverId : driverIds) {
|
|
992
|
+
const auto iterator = registry().find(driverId);
|
|
993
|
+
if (iterator == registry().end() || iterator->second.destroyed ||
|
|
994
|
+
!iterator->second.hasLatest) {
|
|
995
|
+
snapshots.push_back({driverId, unavailablePresentation(), false});
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
const DriverState &state = iterator->second;
|
|
999
|
+
snapshots.push_back(
|
|
1000
|
+
{driverId, visiblePresentation(driverId, state),
|
|
1001
|
+
anyDisplayableView(state)});
|
|
1002
|
+
}
|
|
1003
|
+
return snapshots;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
std::vector<DriverSnapshot> beginGroupInteraction(
|
|
1007
|
+
const std::vector<uint64_t> &driverIds) {
|
|
1008
|
+
if (!isOnMainThread()) return {};
|
|
1009
|
+
std::vector<uint64_t> unique;
|
|
1010
|
+
unique.reserve(driverIds.size());
|
|
1011
|
+
for (const uint64_t driverId : driverIds) {
|
|
1012
|
+
if (driverId == 0 ||
|
|
1013
|
+
std::find(unique.begin(), unique.end(), driverId) != unique.end()) {
|
|
1014
|
+
return {};
|
|
1015
|
+
}
|
|
1016
|
+
const auto iterator = registry().find(driverId);
|
|
1017
|
+
if (iterator == registry().end() || iterator->second.destroyed ||
|
|
1018
|
+
!iterator->second.hasLatest) {
|
|
1019
|
+
return snapshotGroup(driverIds);
|
|
1020
|
+
}
|
|
1021
|
+
unique.push_back(driverId);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
std::vector<int32_t> groupsToCancel;
|
|
1025
|
+
for (const uint64_t driverId : driverIds) {
|
|
1026
|
+
const int32_t groupId = registry().at(driverId).groupId;
|
|
1027
|
+
if (groupId != 0 &&
|
|
1028
|
+
std::find(groupsToCancel.begin(), groupsToCancel.end(), groupId) ==
|
|
1029
|
+
groupsToCancel.end()) {
|
|
1030
|
+
groupsToCancel.push_back(groupId);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
for (const int32_t groupId : groupsToCancel) {
|
|
1034
|
+
finishGroupImpl(groupId, false, false);
|
|
1035
|
+
}
|
|
1036
|
+
for (const uint64_t driverId : driverIds) {
|
|
1037
|
+
DriverState &state = registry().at(driverId);
|
|
1038
|
+
if (state.animation.has_value()) {
|
|
1039
|
+
const int32_t animationId = state.animation->id;
|
|
1040
|
+
const int32_t completionTag = state.animation->completionTag;
|
|
1041
|
+
const Presentation current = state.animation->current;
|
|
1042
|
+
clearActiveAnimation(driverId, state);
|
|
1043
|
+
state.latest = current;
|
|
1044
|
+
state.hasLatest = true;
|
|
1045
|
+
applyToViews(state, current);
|
|
1046
|
+
emitCompletion(driverId, animationId, completionTag, false);
|
|
1047
|
+
}
|
|
1048
|
+
state.ownership = Ownership::Interactive;
|
|
1049
|
+
}
|
|
1050
|
+
return snapshotGroup(driverIds);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
bool setPresentationBatch(const std::vector<BatchEntry> &entries) {
|
|
1054
|
+
if (!isOnMainThread() || entries.empty()) return false;
|
|
1055
|
+
std::vector<BatchEntry> canonicalEntries = entries;
|
|
1056
|
+
for (BatchEntry &entry : canonicalEntries) {
|
|
1057
|
+
if (!canonicalizePresentation(entry.presentation)) return false;
|
|
1058
|
+
}
|
|
1059
|
+
std::vector<uint64_t> unique;
|
|
1060
|
+
std::vector<int32_t> groupsToCancel;
|
|
1061
|
+
unique.reserve(entries.size());
|
|
1062
|
+
for (const BatchEntry &entry : canonicalEntries) {
|
|
1063
|
+
if (entry.driverId == 0 ||
|
|
1064
|
+
std::find(unique.begin(), unique.end(), entry.driverId) != unique.end()) {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
const auto iterator = registry().find(entry.driverId);
|
|
1068
|
+
if (iterator == registry().end() || iterator->second.destroyed ||
|
|
1069
|
+
!isFinitePresentation(entry.presentation)) {
|
|
1070
|
+
return false;
|
|
1071
|
+
}
|
|
1072
|
+
if (iterator != registry().end() && iterator->second.groupId != 0 &&
|
|
1073
|
+
std::find(
|
|
1074
|
+
groupsToCancel.begin(),
|
|
1075
|
+
groupsToCancel.end(),
|
|
1076
|
+
iterator->second.groupId) == groupsToCancel.end()) {
|
|
1077
|
+
groupsToCancel.push_back(iterator->second.groupId);
|
|
1078
|
+
}
|
|
1079
|
+
unique.push_back(entry.driverId);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// Replacement is all-or-nothing: validation above is deliberately complete
|
|
1083
|
+
// before any old group is dissolved. An overlap dissolves the whole immutable
|
|
1084
|
+
// old group (including members absent from this batch) exactly once.
|
|
1085
|
+
for (const int32_t groupId : groupsToCancel) {
|
|
1086
|
+
finishGroupImpl(groupId, false, false);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const double now = nowSeconds();
|
|
1090
|
+
for (const BatchEntry &entry : canonicalEntries) {
|
|
1091
|
+
DriverState &state = registry().at(entry.driverId);
|
|
1092
|
+
state.destroyed = false;
|
|
1093
|
+
finishActive(entry.driverId, state, false);
|
|
1094
|
+
state.latest = entry.presentation;
|
|
1095
|
+
state.hasLatest = true;
|
|
1096
|
+
state.ownership = Ownership::Interactive;
|
|
1097
|
+
recordVelocitySample(
|
|
1098
|
+
state.samples, velocityChannels(entry.presentation), now);
|
|
1099
|
+
}
|
|
1100
|
+
for (const BatchEntry &entry : canonicalEntries) {
|
|
1101
|
+
applyToViews(registry().at(entry.driverId), entry.presentation);
|
|
1102
|
+
}
|
|
1103
|
+
return true;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
namespace {
|
|
1107
|
+
|
|
1108
|
+
int32_t startGroupCommon(
|
|
1109
|
+
uint64_t controllerId,
|
|
1110
|
+
std::vector<GroupMotionEntry> entries,
|
|
1111
|
+
AnimationKind kind,
|
|
1112
|
+
TimingAnimation timing,
|
|
1113
|
+
SpringAnimation spring,
|
|
1114
|
+
double durationMs,
|
|
1115
|
+
int32_t reduceMotion,
|
|
1116
|
+
int32_t completionTag,
|
|
1117
|
+
double startedAtHintS) {
|
|
1118
|
+
if (!isOnMainThread() || controllerId == 0 || entries.empty()) {
|
|
1119
|
+
return 0;
|
|
1120
|
+
}
|
|
1121
|
+
for (GroupMotionEntry &entry : entries) {
|
|
1122
|
+
if ((entry.hasFrom && !canonicalizePresentation(entry.from)) ||
|
|
1123
|
+
!canonicalizePresentation(entry.target)) {
|
|
1124
|
+
return 0;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
if (kind == AnimationKind::Timing &&
|
|
1128
|
+
!isValidTiming(timing)) {
|
|
1129
|
+
return 0;
|
|
1130
|
+
}
|
|
1131
|
+
if (kind == AnimationKind::Spring &&
|
|
1132
|
+
(!isValidSpring(spring) || spring.inheritVelocity)) {
|
|
1133
|
+
return 0;
|
|
1134
|
+
}
|
|
1135
|
+
const bool reduce = shouldReduceMotion(reduceMotion) ||
|
|
1136
|
+
(kind != AnimationKind::Spring && durationMs <= 0);
|
|
1137
|
+
std::vector<uint64_t> unique;
|
|
1138
|
+
std::vector<int32_t> groupsToCancel;
|
|
1139
|
+
std::vector<Presentation> resolvedStarts;
|
|
1140
|
+
unique.reserve(entries.size());
|
|
1141
|
+
resolvedStarts.reserve(entries.size());
|
|
1142
|
+
for (const GroupMotionEntry &entry : entries) {
|
|
1143
|
+
if (entry.driverId == 0 ||
|
|
1144
|
+
std::find(unique.begin(), unique.end(), entry.driverId) != unique.end()) {
|
|
1145
|
+
return 0;
|
|
1146
|
+
}
|
|
1147
|
+
const auto iterator = registry().find(entry.driverId);
|
|
1148
|
+
if ((iterator == registry().end() || iterator->second.destroyed ||
|
|
1149
|
+
!iterator->second.hasLatest) && !entry.hasFrom) {
|
|
1150
|
+
return 0;
|
|
1151
|
+
}
|
|
1152
|
+
if (iterator != registry().end() && iterator->second.groupId != 0 &&
|
|
1153
|
+
std::find(
|
|
1154
|
+
groupsToCancel.begin(),
|
|
1155
|
+
groupsToCancel.end(),
|
|
1156
|
+
iterator->second.groupId) == groupsToCancel.end()) {
|
|
1157
|
+
groupsToCancel.push_back(iterator->second.groupId);
|
|
1158
|
+
}
|
|
1159
|
+
const Presentation resolvedStart = entry.hasFrom
|
|
1160
|
+
? entry.from
|
|
1161
|
+
: visiblePresentation(entry.driverId, iterator->second);
|
|
1162
|
+
if (!isFinitePresentation(resolvedStart) ||
|
|
1163
|
+
!isFinitePresentation(entry.target) ||
|
|
1164
|
+
!isAutonomousUniformCircular(resolvedStart) ||
|
|
1165
|
+
!isAutonomousUniformCircular(entry.target)) {
|
|
1166
|
+
return 0;
|
|
1167
|
+
}
|
|
1168
|
+
if (kind == AnimationKind::Spring) {
|
|
1169
|
+
const double velocity = spring.initialVelocity;
|
|
1170
|
+
if (!springScaleStaysPositive(
|
|
1171
|
+
resolvedStart, entry.target, spring, velocity)) {
|
|
1172
|
+
return 0;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
resolvedStarts.push_back(resolvedStart);
|
|
1176
|
+
unique.push_back(entry.driverId);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// Capture every implicit start above, while overlapped groups still own
|
|
1180
|
+
// their current frames. Only after full preflight may replacement dissolve
|
|
1181
|
+
// those immutable groups and emit one unfinished completion for each.
|
|
1182
|
+
for (const int32_t groupId : groupsToCancel) {
|
|
1183
|
+
finishGroupImpl(groupId, false, false);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
ActiveGroup group;
|
|
1187
|
+
group.controllerId = controllerId;
|
|
1188
|
+
group.id = allocateAnimationId();
|
|
1189
|
+
group.completionTag = completionTag;
|
|
1190
|
+
group.kind = kind;
|
|
1191
|
+
group.timing = timing;
|
|
1192
|
+
group.spring = spring;
|
|
1193
|
+
group.scalarSpring = {0, spring.initialVelocity};
|
|
1194
|
+
group.durationS = durationMs / 1000.0;
|
|
1195
|
+
group.members.reserve(entries.size());
|
|
1196
|
+
const double wallNow = nowSeconds();
|
|
1197
|
+
|
|
1198
|
+
for (std::size_t entryIndex = 0; entryIndex < entries.size(); entryIndex += 1) {
|
|
1199
|
+
GroupMotionEntry &entry = entries[entryIndex];
|
|
1200
|
+
DriverState &state = registry()[entry.driverId];
|
|
1201
|
+
const Presentation resolvedStart = resolvedStarts[entryIndex];
|
|
1202
|
+
finishActive(entry.driverId, state, false);
|
|
1203
|
+
state.destroyed = false;
|
|
1204
|
+
state.latest = entry.target;
|
|
1205
|
+
state.hasLatest = true;
|
|
1206
|
+
state.ownership = Ownership::Native;
|
|
1207
|
+
state.groupId = group.id;
|
|
1208
|
+
|
|
1209
|
+
GroupMemberAnimation member;
|
|
1210
|
+
member.driverId = entry.driverId;
|
|
1211
|
+
member.animation.id = group.id;
|
|
1212
|
+
member.animation.kind = kind;
|
|
1213
|
+
member.animation.start = resolvedStart;
|
|
1214
|
+
member.animation.target = entry.target;
|
|
1215
|
+
member.animation.current = resolvedStart;
|
|
1216
|
+
member.animation.timing = timing;
|
|
1217
|
+
member.animation.spring = spring;
|
|
1218
|
+
member.animation.durationS = group.durationS;
|
|
1219
|
+
group.members.push_back(std::move(member));
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Apply every explicit start after the whole participant set has passed
|
|
1223
|
+
// preflight and acquired the new immutable group id. This remains one
|
|
1224
|
+
// synchronous main-thread transaction even when the initial readiness
|
|
1225
|
+
// barrier is pending; ready hosts must not keep displaying their old frame
|
|
1226
|
+
// while snapshots already report `from`.
|
|
1227
|
+
for (std::size_t entryIndex = 0; entryIndex < entries.size(); entryIndex += 1) {
|
|
1228
|
+
if (!entries[entryIndex].hasFrom) continue;
|
|
1229
|
+
applyToViews(
|
|
1230
|
+
registry().at(entries[entryIndex].driverId),
|
|
1231
|
+
resolvedStarts[entryIndex]);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
const int32_t groupId = group.id;
|
|
1235
|
+
const StartStamp stamp = resolveStartStamp(startedAtHintS, wallNow);
|
|
1236
|
+
group.startedAtS = stamp.startedAtS;
|
|
1237
|
+
group.lastFrameS = stamp.startedAtS;
|
|
1238
|
+
group.frameClockAnchored = stamp.frameClockAnchored;
|
|
1239
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
1240
|
+
member.animation.startedAtS = stamp.startedAtS;
|
|
1241
|
+
member.animation.lastFrameS = stamp.startedAtS;
|
|
1242
|
+
member.animation.frameClockAnchored = stamp.frameClockAnchored;
|
|
1243
|
+
}
|
|
1244
|
+
group.started = allGroupMembersReady(group);
|
|
1245
|
+
groupRegistry().emplace(groupId, std::move(group));
|
|
1246
|
+
if (reduce) {
|
|
1247
|
+
finishGroupImpl(groupId, true, true);
|
|
1248
|
+
return groupId;
|
|
1249
|
+
}
|
|
1250
|
+
ActiveGroup &stored = groupRegistry().at(groupId);
|
|
1251
|
+
if (stored.started) {
|
|
1252
|
+
startGroupFrameLoop(stored);
|
|
1253
|
+
}
|
|
1254
|
+
return groupId;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
} // namespace
|
|
1258
|
+
|
|
1259
|
+
int32_t animateTimingGroup(
|
|
1260
|
+
uint64_t controllerId,
|
|
1261
|
+
std::vector<GroupMotionEntry> entries,
|
|
1262
|
+
TimingAnimation animation,
|
|
1263
|
+
int32_t completionTag,
|
|
1264
|
+
double startedAtHintS) {
|
|
1265
|
+
return startGroupCommon(
|
|
1266
|
+
controllerId,
|
|
1267
|
+
std::move(entries),
|
|
1268
|
+
AnimationKind::Timing,
|
|
1269
|
+
animation,
|
|
1270
|
+
{},
|
|
1271
|
+
animation.durationMs,
|
|
1272
|
+
animation.reduceMotion,
|
|
1273
|
+
completionTag,
|
|
1274
|
+
startedAtHintS);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
int32_t animateSpringGroup(
|
|
1278
|
+
uint64_t controllerId,
|
|
1279
|
+
std::vector<GroupMotionEntry> entries,
|
|
1280
|
+
SpringAnimation animation,
|
|
1281
|
+
int32_t completionTag,
|
|
1282
|
+
double startedAtHintS) {
|
|
1283
|
+
return startGroupCommon(
|
|
1284
|
+
controllerId,
|
|
1285
|
+
std::move(entries),
|
|
1286
|
+
AnimationKind::Spring,
|
|
1287
|
+
{},
|
|
1288
|
+
animation,
|
|
1289
|
+
kSpringMaxDurationS * 1000.0,
|
|
1290
|
+
animation.reduceMotion,
|
|
1291
|
+
completionTag,
|
|
1292
|
+
startedAtHintS);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
std::vector<DriverSnapshot> cancelAnimationGroup(
|
|
1296
|
+
int32_t groupId,
|
|
1297
|
+
GroupCancelBehavior behavior) {
|
|
1298
|
+
if (!isOnMainThread()) return {};
|
|
1299
|
+
const bool finish = behavior == GroupCancelBehavior::Finish;
|
|
1300
|
+
return finishGroupImpl(
|
|
1301
|
+
groupId, finish, finish);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
676
1304
|
void setPresentation(
|
|
677
1305
|
uint64_t driverId,
|
|
678
1306
|
Presentation presentation,
|
|
@@ -680,6 +1308,7 @@ void setPresentation(
|
|
|
680
1308
|
bool overridePendingAnimation,
|
|
681
1309
|
bool recordVelocity) {
|
|
682
1310
|
if (!isOnMainThread()) return;
|
|
1311
|
+
if (!canonicalizePresentation(presentation)) return;
|
|
683
1312
|
SMOOTH_CLIP_TRACE("SmoothClip.setPresentation");
|
|
684
1313
|
auto iterator = registry().find(driverId);
|
|
685
1314
|
if (!takeOwnership) {
|
|
@@ -692,12 +1321,16 @@ void setPresentation(
|
|
|
692
1321
|
iterator = registry().try_emplace(driverId).first;
|
|
693
1322
|
}
|
|
694
1323
|
auto &state = iterator->second;
|
|
1324
|
+
if (state.groupId != 0) {
|
|
1325
|
+
if (!takeOwnership) return;
|
|
1326
|
+
finishGroupImpl(state.groupId, false, false);
|
|
1327
|
+
}
|
|
695
1328
|
if (!takeOwnership && state.ownership != Ownership::Interactive) return;
|
|
696
1329
|
if (takeOwnership) {
|
|
697
1330
|
state.destroyed = false;
|
|
698
1331
|
// Passive seeds and public setters must not displace newer pending intent.
|
|
699
1332
|
// The fused animation.from write is different: it is the caller's newest,
|
|
700
|
-
// authoritative start and explicitly
|
|
1333
|
+
// authoritative start and explicitly cancels the pending run.
|
|
701
1334
|
if (state.animation.has_value() && !state.animation->started &&
|
|
702
1335
|
!overridePendingAnimation) {
|
|
703
1336
|
return;
|
|
@@ -708,7 +1341,8 @@ void setPresentation(
|
|
|
708
1341
|
state.hasLatest = true;
|
|
709
1342
|
state.ownership = Ownership::Interactive;
|
|
710
1343
|
if (recordVelocity) {
|
|
711
|
-
recordVelocitySample(
|
|
1344
|
+
recordVelocitySample(
|
|
1345
|
+
state.samples, velocityChannels(presentation), nowSeconds());
|
|
712
1346
|
} else if (state.samples.hasLatest) {
|
|
713
1347
|
// Untracked movement invalidates the recorded velocity pair.
|
|
714
1348
|
clearVelocitySamples(state.samples);
|
|
@@ -721,12 +1355,16 @@ Presentation beginInteraction(uint64_t driverId) {
|
|
|
721
1355
|
auto iterator = registry().find(driverId);
|
|
722
1356
|
if (iterator == registry().end()) return unavailablePresentation();
|
|
723
1357
|
auto &state = iterator->second;
|
|
1358
|
+
if (state.groupId != 0) {
|
|
1359
|
+
finishGroupImpl(state.groupId, false, false);
|
|
1360
|
+
}
|
|
724
1361
|
state.destroyed = false;
|
|
725
1362
|
if (!state.animation.has_value()) {
|
|
726
1363
|
state.ownership = Ownership::Interactive;
|
|
727
1364
|
return state.latest;
|
|
728
1365
|
}
|
|
729
1366
|
const int32_t animationId = state.animation->id;
|
|
1367
|
+
const int32_t completionTag = state.animation->completionTag;
|
|
730
1368
|
const Presentation current = state.animation->current;
|
|
731
1369
|
clearActiveAnimation(driverId, state);
|
|
732
1370
|
state.latest = current;
|
|
@@ -735,7 +1373,7 @@ Presentation beginInteraction(uint64_t driverId) {
|
|
|
735
1373
|
// The frozen frame came from native animation movement, not an interactive
|
|
736
1374
|
// write. Do not let it become a later `initialVelocity: inherit` sample.
|
|
737
1375
|
applyToViews(state, current);
|
|
738
|
-
emitCompletion(driverId, animationId, false);
|
|
1376
|
+
emitCompletion(driverId, animationId, completionTag, false);
|
|
739
1377
|
return current;
|
|
740
1378
|
}
|
|
741
1379
|
|
|
@@ -743,27 +1381,57 @@ int32_t animateTiming(
|
|
|
743
1381
|
uint64_t driverId,
|
|
744
1382
|
AnimationStart start,
|
|
745
1383
|
Presentation presentation,
|
|
746
|
-
TimingAnimation animation
|
|
747
|
-
|
|
1384
|
+
TimingAnimation animation,
|
|
1385
|
+
int32_t completionTag) {
|
|
1386
|
+
const bool validAnimation = isValidTiming(animation);
|
|
1387
|
+
if (!canonicalizePresentation(presentation) ||
|
|
1388
|
+
(start.hasInteractiveStart &&
|
|
1389
|
+
!canonicalizePresentation(start.interactiveStart))) {
|
|
1390
|
+
return 0;
|
|
1391
|
+
}
|
|
1392
|
+
if (!isOnMainThread() || driverId == 0 ||
|
|
1393
|
+
!isFinitePresentation(presentation) || !validAnimation) {
|
|
1394
|
+
return 0;
|
|
1395
|
+
}
|
|
748
1396
|
auto iterator = registry().find(driverId);
|
|
1397
|
+
Presentation preflightStart;
|
|
1398
|
+
if (iterator == registry().end()) {
|
|
1399
|
+
if (!start.hasInteractiveStart ||
|
|
1400
|
+
!isFinitePresentation(start.interactiveStart)) {
|
|
1401
|
+
return 0;
|
|
1402
|
+
}
|
|
1403
|
+
preflightStart = start.interactiveStart;
|
|
1404
|
+
} else {
|
|
1405
|
+
if (iterator->second.destroyed && !start.hasInteractiveStart) return 0;
|
|
1406
|
+
preflightStart = start.hasInteractiveStart
|
|
1407
|
+
? start.interactiveStart
|
|
1408
|
+
: visiblePresentation(driverId, iterator->second);
|
|
1409
|
+
}
|
|
1410
|
+
if (!isFinitePresentation(preflightStart) ||
|
|
1411
|
+
preflightStart.clip.curve != presentation.clip.curve) {
|
|
1412
|
+
return 0;
|
|
1413
|
+
}
|
|
1414
|
+
const bool reduce = shouldReduceMotion(animation.reduceMotion) ||
|
|
1415
|
+
animation.durationMs <= 0;
|
|
749
1416
|
if (iterator == registry().end()) {
|
|
750
|
-
if (!start.hasInteractiveStart) return 0;
|
|
751
1417
|
iterator = registry().try_emplace(driverId).first;
|
|
752
1418
|
iterator->second.latest = start.interactiveStart;
|
|
753
1419
|
iterator->second.hasLatest = true;
|
|
754
1420
|
}
|
|
755
1421
|
auto &state = iterator->second;
|
|
1422
|
+
if (state.groupId != 0) finishGroupImpl(state.groupId, false, false);
|
|
756
1423
|
state.destroyed = false;
|
|
757
1424
|
const int32_t animationId = allocateAnimationId();
|
|
758
1425
|
const Presentation resolvedStart =
|
|
759
1426
|
prepareAnimation(driverId, state, start, presentation);
|
|
760
|
-
if (
|
|
1427
|
+
if (reduce) {
|
|
761
1428
|
applyPresentation(state, presentation);
|
|
762
|
-
emitCompletion(driverId, animationId, true);
|
|
1429
|
+
emitCompletion(driverId, animationId, completionTag, true);
|
|
763
1430
|
return animationId;
|
|
764
1431
|
}
|
|
765
1432
|
ActiveAnimation active;
|
|
766
1433
|
active.id = animationId;
|
|
1434
|
+
active.completionTag = completionTag;
|
|
767
1435
|
active.kind = AnimationKind::Timing;
|
|
768
1436
|
active.timing = animation;
|
|
769
1437
|
active.start = resolvedStart;
|
|
@@ -776,33 +1444,67 @@ int32_t animateSpring(
|
|
|
776
1444
|
uint64_t driverId,
|
|
777
1445
|
AnimationStart start,
|
|
778
1446
|
Presentation presentation,
|
|
779
|
-
SpringAnimation animation
|
|
780
|
-
|
|
1447
|
+
SpringAnimation animation,
|
|
1448
|
+
int32_t completionTag) {
|
|
1449
|
+
const bool validAnimation = isValidSpring(animation);
|
|
1450
|
+
if (!canonicalizePresentation(presentation) ||
|
|
1451
|
+
(start.hasInteractiveStart &&
|
|
1452
|
+
!canonicalizePresentation(start.interactiveStart))) {
|
|
1453
|
+
return 0;
|
|
1454
|
+
}
|
|
1455
|
+
if (!isOnMainThread() || driverId == 0 ||
|
|
1456
|
+
!isFinitePresentation(presentation) || !validAnimation) {
|
|
1457
|
+
return 0;
|
|
1458
|
+
}
|
|
781
1459
|
auto iterator = registry().find(driverId);
|
|
1460
|
+
Presentation preflightStart;
|
|
1461
|
+
if (iterator == registry().end()) {
|
|
1462
|
+
if (!start.hasInteractiveStart ||
|
|
1463
|
+
!isFinitePresentation(start.interactiveStart)) {
|
|
1464
|
+
return 0;
|
|
1465
|
+
}
|
|
1466
|
+
preflightStart = start.interactiveStart;
|
|
1467
|
+
} else {
|
|
1468
|
+
if (iterator->second.destroyed && !start.hasInteractiveStart) return 0;
|
|
1469
|
+
preflightStart = start.hasInteractiveStart
|
|
1470
|
+
? start.interactiveStart
|
|
1471
|
+
: visiblePresentation(driverId, iterator->second);
|
|
1472
|
+
}
|
|
1473
|
+
const double velocity = animation.inheritVelocity &&
|
|
1474
|
+
iterator != registry().end()
|
|
1475
|
+
? inheritedVelocity(
|
|
1476
|
+
iterator->second.samples,
|
|
1477
|
+
velocityChannels(presentation),
|
|
1478
|
+
nowSeconds())
|
|
1479
|
+
: animation.initialVelocity;
|
|
1480
|
+
if (!isFinitePresentation(preflightStart) ||
|
|
1481
|
+
preflightStart.clip.curve != presentation.clip.curve ||
|
|
1482
|
+
!springScaleStaysPositive(
|
|
1483
|
+
preflightStart, presentation, animation, velocity)) {
|
|
1484
|
+
return 0;
|
|
1485
|
+
}
|
|
782
1486
|
if (iterator == registry().end()) {
|
|
783
|
-
if (!start.hasInteractiveStart) return 0;
|
|
784
1487
|
iterator = registry().try_emplace(driverId).first;
|
|
785
1488
|
iterator->second.latest = start.interactiveStart;
|
|
786
1489
|
iterator->second.hasLatest = true;
|
|
787
1490
|
}
|
|
788
1491
|
auto &state = iterator->second;
|
|
1492
|
+
if (state.groupId != 0) finishGroupImpl(state.groupId, false, false);
|
|
789
1493
|
state.destroyed = false;
|
|
790
1494
|
const int32_t animationId = allocateAnimationId();
|
|
791
1495
|
// Scalar velocity along the current-to-target trajectory; each channel is
|
|
792
1496
|
// seeded with velocity·displacement to match the iOS CASpringAnimation
|
|
793
1497
|
// per-keypath behavior.
|
|
794
|
-
const double velocity = animation.inheritVelocity
|
|
795
|
-
? inheritedVelocity(state.samples, toChannels(presentation), nowSeconds())
|
|
796
|
-
: animation.initialVelocity;
|
|
797
1498
|
const Presentation resolvedStart =
|
|
798
1499
|
prepareAnimation(driverId, state, start, presentation);
|
|
799
1500
|
if (shouldReduceMotion(animation.reduceMotion)) {
|
|
800
1501
|
applyPresentation(state, presentation);
|
|
801
|
-
emitCompletion(driverId, animationId, true);
|
|
1502
|
+
emitCompletion(driverId, animationId, completionTag, true);
|
|
802
1503
|
return animationId;
|
|
803
1504
|
}
|
|
804
1505
|
ActiveAnimation active;
|
|
805
1506
|
active.id = animationId;
|
|
1507
|
+
active.completionTag = completionTag;
|
|
806
1508
|
active.kind = AnimationKind::Spring;
|
|
807
1509
|
active.spring = animation;
|
|
808
1510
|
active.spring.initialVelocity = velocity;
|
|
@@ -810,58 +1512,31 @@ int32_t animateSpring(
|
|
|
810
1512
|
active.start = resolvedStart;
|
|
811
1513
|
active.target = presentation;
|
|
812
1514
|
active.durationS = kSpringMaxDurationS;
|
|
813
|
-
const auto
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1515
|
+
const auto normalizedEndpoints = normalizeShadowEndpoints(
|
|
1516
|
+
resolvedStart, presentation);
|
|
1517
|
+
const auto startChannels = toChannels(normalizedEndpoints.first);
|
|
1518
|
+
const auto targetChannels = toChannels(normalizedEndpoints.second);
|
|
1519
|
+
active.springChannelCount =
|
|
1520
|
+
(resolvedStart.shadow.enabled && resolvedStart.shadow.alpha > 0) ||
|
|
1521
|
+
(presentation.shadow.enabled && presentation.shadow.alpha > 0)
|
|
1522
|
+
? kChannelCount
|
|
1523
|
+
: kBaseChannelCount;
|
|
1524
|
+
active.springPosition = startChannels;
|
|
1525
|
+
for (std::size_t index = 0;
|
|
1526
|
+
index < active.springChannelCount;
|
|
1527
|
+
index += 1) {
|
|
817
1528
|
active.springVelocity[index] =
|
|
818
1529
|
velocity * (targetChannels[index] - startChannels[index]);
|
|
819
1530
|
}
|
|
820
1531
|
return startAnimation(driverId, state, std::move(active), start.startedAtHintS);
|
|
821
1532
|
}
|
|
822
1533
|
|
|
823
|
-
int32_t animateKeyframes(
|
|
824
|
-
uint64_t driverId,
|
|
825
|
-
AnimationStart start,
|
|
826
|
-
Presentation presentation,
|
|
827
|
-
double durationMs,
|
|
828
|
-
std::vector<Keyframe> keyframes,
|
|
829
|
-
int32_t reduceMotion) {
|
|
830
|
-
if (!isOnMainThread()) return 0;
|
|
831
|
-
auto iterator = registry().find(driverId);
|
|
832
|
-
if (iterator == registry().end()) {
|
|
833
|
-
if (!start.hasInteractiveStart) return 0;
|
|
834
|
-
iterator = registry().try_emplace(driverId).first;
|
|
835
|
-
iterator->second.latest = start.interactiveStart;
|
|
836
|
-
iterator->second.hasLatest = true;
|
|
837
|
-
}
|
|
838
|
-
auto &state = iterator->second;
|
|
839
|
-
state.destroyed = false;
|
|
840
|
-
const int32_t animationId = allocateAnimationId();
|
|
841
|
-
const Presentation resolvedStart =
|
|
842
|
-
prepareAnimation(driverId, state, start, presentation);
|
|
843
|
-
if (shouldReduceMotion(reduceMotion) || durationMs <= 0 ||
|
|
844
|
-
keyframes.size() < 2) {
|
|
845
|
-
applyPresentation(state, presentation);
|
|
846
|
-
emitCompletion(driverId, animationId, true);
|
|
847
|
-
return animationId;
|
|
848
|
-
}
|
|
849
|
-
ActiveAnimation active;
|
|
850
|
-
active.id = animationId;
|
|
851
|
-
active.kind = AnimationKind::Keyframes;
|
|
852
|
-
active.keyframes.reset(std::move(keyframes));
|
|
853
|
-
active.start = resolvedStart;
|
|
854
|
-
active.target = presentation;
|
|
855
|
-
active.durationS = durationMs / 1000.0;
|
|
856
|
-
return startAnimation(driverId, state, std::move(active), start.startedAtHintS);
|
|
857
|
-
}
|
|
858
|
-
|
|
859
1534
|
int32_t rejectAnimation(uint64_t driverId) {
|
|
860
1535
|
if (!isOnMainThread()) return 0;
|
|
861
1536
|
auto iterator = registry().find(driverId);
|
|
862
1537
|
if (iterator == registry().end()) return 0;
|
|
863
1538
|
const int32_t animationId = allocateAnimationId();
|
|
864
|
-
emitCompletion(driverId, animationId, false);
|
|
1539
|
+
emitCompletion(driverId, animationId, 0, false);
|
|
865
1540
|
return animationId;
|
|
866
1541
|
}
|
|
867
1542
|
|
|
@@ -875,6 +1550,18 @@ CancelResult cancelAnimation(
|
|
|
875
1550
|
return {false, {{0, 0, 0, 0, 0}, 0, 0}};
|
|
876
1551
|
}
|
|
877
1552
|
auto &state = iterator->second;
|
|
1553
|
+
if (state.groupId != 0) {
|
|
1554
|
+
const int32_t activeGroupId = state.groupId;
|
|
1555
|
+
if (animationId > 0 && animationId != activeGroupId) {
|
|
1556
|
+
return {false, visiblePresentation(driverId, state)};
|
|
1557
|
+
}
|
|
1558
|
+
const std::vector<DriverSnapshot> snapshots =
|
|
1559
|
+
finishGroupImpl(activeGroupId, useTarget, false);
|
|
1560
|
+
for (const DriverSnapshot &snapshot : snapshots) {
|
|
1561
|
+
if (snapshot.driverId == driverId) return {true, snapshot.presentation};
|
|
1562
|
+
}
|
|
1563
|
+
return {false, unavailablePresentation()};
|
|
1564
|
+
}
|
|
878
1565
|
if (!state.animation.has_value() ||
|
|
879
1566
|
(animationId > 0 && animationId != state.animation->id)) {
|
|
880
1567
|
return {false, state.animation.has_value() ? state.animation->current
|
|
@@ -883,12 +1570,13 @@ CancelResult cancelAnimation(
|
|
|
883
1570
|
const Presentation result =
|
|
884
1571
|
useTarget ? state.animation->target : state.animation->current;
|
|
885
1572
|
const int32_t activeId = state.animation->id;
|
|
1573
|
+
const int32_t completionTag = state.animation->completionTag;
|
|
886
1574
|
clearActiveAnimation(driverId, state);
|
|
887
1575
|
state.latest = result;
|
|
888
1576
|
state.hasLatest = true;
|
|
889
1577
|
state.ownership = Ownership::Interactive;
|
|
890
1578
|
applyToViews(state, result);
|
|
891
|
-
emitCompletion(driverId, activeId, false);
|
|
1579
|
+
emitCompletion(driverId, activeId, completionTag, false);
|
|
892
1580
|
return {true, result};
|
|
893
1581
|
}
|
|
894
1582
|
|
|
@@ -897,6 +1585,7 @@ void destroyDriver(uint64_t driverId) {
|
|
|
897
1585
|
auto iterator = registry().find(driverId);
|
|
898
1586
|
if (iterator == registry().end()) return;
|
|
899
1587
|
auto &state = iterator->second;
|
|
1588
|
+
if (state.groupId != 0) finishGroupImpl(state.groupId, false, false);
|
|
900
1589
|
state.ownership = Ownership::Interactive;
|
|
901
1590
|
finishActive(driverId, state, false);
|
|
902
1591
|
// A destroyed driver's interaction history must not seed a revived
|
|
@@ -921,37 +1610,115 @@ void JSmoothClipView::applyClip(const Presentation &presentation) const {
|
|
|
921
1610
|
static const auto method =
|
|
922
1611
|
javaClassStatic()
|
|
923
1612
|
->getMethod<void(
|
|
924
|
-
jdouble,
|
|
925
|
-
|
|
1613
|
+
jdouble,
|
|
1614
|
+
jdouble,
|
|
1615
|
+
jdouble,
|
|
1616
|
+
jdouble,
|
|
1617
|
+
jdouble,
|
|
1618
|
+
jdouble,
|
|
1619
|
+
jdouble,
|
|
1620
|
+
jdouble,
|
|
1621
|
+
jint,
|
|
1622
|
+
jdouble,
|
|
1623
|
+
jdouble,
|
|
1624
|
+
jdouble,
|
|
1625
|
+
jboolean,
|
|
1626
|
+
jdouble,
|
|
1627
|
+
jdouble,
|
|
1628
|
+
jdouble,
|
|
1629
|
+
jdouble,
|
|
1630
|
+
jdouble,
|
|
1631
|
+
jdouble,
|
|
1632
|
+
jdouble,
|
|
1633
|
+
jdouble)>("setClipPresentationDip");
|
|
926
1634
|
method(
|
|
927
1635
|
self(),
|
|
928
1636
|
presentation.clip.x,
|
|
929
1637
|
presentation.clip.y,
|
|
930
1638
|
presentation.clip.width,
|
|
931
1639
|
presentation.clip.height,
|
|
932
|
-
|
|
1640
|
+
SmoothClipResolvedRadius(
|
|
1641
|
+
presentation.clip.topLeftRadius, presentation.clip.radius),
|
|
1642
|
+
SmoothClipResolvedRadius(
|
|
1643
|
+
presentation.clip.topRightRadius, presentation.clip.radius),
|
|
1644
|
+
SmoothClipResolvedRadius(
|
|
1645
|
+
presentation.clip.bottomRightRadius, presentation.clip.radius),
|
|
1646
|
+
SmoothClipResolvedRadius(
|
|
1647
|
+
presentation.clip.bottomLeftRadius, presentation.clip.radius),
|
|
1648
|
+
static_cast<jint>(presentation.clip.curve),
|
|
933
1649
|
presentation.contentTranslateX,
|
|
934
|
-
presentation.contentTranslateY
|
|
1650
|
+
presentation.contentTranslateY,
|
|
1651
|
+
presentation.contentScale,
|
|
1652
|
+
presentation.shadow.enabled,
|
|
1653
|
+
presentation.shadow.red,
|
|
1654
|
+
presentation.shadow.green,
|
|
1655
|
+
presentation.shadow.blue,
|
|
1656
|
+
presentation.shadow.alpha,
|
|
1657
|
+
presentation.shadow.offsetX,
|
|
1658
|
+
presentation.shadow.offsetY,
|
|
1659
|
+
presentation.shadow.blurRadius,
|
|
1660
|
+
presentation.shadow.spreadDistance);
|
|
935
1661
|
}
|
|
936
1662
|
|
|
937
1663
|
void JSmoothClipView::applyClipPx(
|
|
938
|
-
const
|
|
1664
|
+
const CanonicalClip &clip,
|
|
939
1665
|
double contentTranslateXPx,
|
|
940
|
-
double contentTranslateYPx
|
|
1666
|
+
double contentTranslateYPx,
|
|
1667
|
+
double contentScale,
|
|
1668
|
+
const Shadow &shadowPx) const {
|
|
941
1669
|
static const auto method =
|
|
942
1670
|
javaClassStatic()
|
|
943
1671
|
->getMethod<void(
|
|
944
|
-
jfloat,
|
|
945
|
-
|
|
1672
|
+
jfloat,
|
|
1673
|
+
jfloat,
|
|
1674
|
+
jfloat,
|
|
1675
|
+
jfloat,
|
|
1676
|
+
jfloat,
|
|
1677
|
+
jfloat,
|
|
1678
|
+
jfloat,
|
|
1679
|
+
jfloat,
|
|
1680
|
+
jint,
|
|
1681
|
+
jfloat,
|
|
1682
|
+
jfloat,
|
|
1683
|
+
jfloat,
|
|
1684
|
+
jboolean,
|
|
1685
|
+
jfloat,
|
|
1686
|
+
jfloat,
|
|
1687
|
+
jfloat,
|
|
1688
|
+
jfloat,
|
|
1689
|
+
jfloat,
|
|
1690
|
+
jfloat,
|
|
1691
|
+
jfloat,
|
|
1692
|
+
jfloat)>("setClipPresentationPx");
|
|
946
1693
|
method(
|
|
947
1694
|
self(),
|
|
948
1695
|
static_cast<jfloat>(clip.left),
|
|
949
1696
|
static_cast<jfloat>(clip.top),
|
|
950
1697
|
static_cast<jfloat>(clip.right),
|
|
951
1698
|
static_cast<jfloat>(clip.bottom),
|
|
952
|
-
static_cast<jfloat>(clip.
|
|
1699
|
+
static_cast<jfloat>(clip.topLeftRadius),
|
|
1700
|
+
static_cast<jfloat>(clip.topRightRadius),
|
|
1701
|
+
static_cast<jfloat>(clip.bottomRightRadius),
|
|
1702
|
+
static_cast<jfloat>(clip.bottomLeftRadius),
|
|
1703
|
+
static_cast<jint>(clip.curve),
|
|
953
1704
|
static_cast<jfloat>(contentTranslateXPx),
|
|
954
|
-
static_cast<jfloat>(contentTranslateYPx)
|
|
1705
|
+
static_cast<jfloat>(contentTranslateYPx),
|
|
1706
|
+
static_cast<jfloat>(contentScale),
|
|
1707
|
+
shadowPx.enabled,
|
|
1708
|
+
static_cast<jfloat>(shadowPx.red),
|
|
1709
|
+
static_cast<jfloat>(shadowPx.green),
|
|
1710
|
+
static_cast<jfloat>(shadowPx.blue),
|
|
1711
|
+
static_cast<jfloat>(shadowPx.alpha),
|
|
1712
|
+
static_cast<jfloat>(shadowPx.offsetX),
|
|
1713
|
+
static_cast<jfloat>(shadowPx.offsetY),
|
|
1714
|
+
static_cast<jfloat>(shadowPx.blurRadius),
|
|
1715
|
+
static_cast<jfloat>(shadowPx.spreadDistance));
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
void JSmoothClipView::setAutonomousMotion(bool active) const {
|
|
1719
|
+
static const auto method =
|
|
1720
|
+
javaClassStatic()->getMethod<void(jboolean)>("setAutonomousMotion");
|
|
1721
|
+
method(self(), active);
|
|
955
1722
|
}
|
|
956
1723
|
|
|
957
1724
|
void registerViewAndroid(
|
|
@@ -962,15 +1729,14 @@ void registerViewAndroid(
|
|
|
962
1729
|
double hostWidthPx,
|
|
963
1730
|
double hostHeightPx,
|
|
964
1731
|
bool lifecycleVisible) {
|
|
1732
|
+
if (!canonicalizePresentation(initialPresentation)) return;
|
|
965
1733
|
auto &state = registry()[driverId];
|
|
966
1734
|
state.destroyed = false;
|
|
967
1735
|
if (!state.hasLatest) {
|
|
968
1736
|
state.latest = initialPresentation;
|
|
969
1737
|
state.hasLatest = true;
|
|
970
1738
|
}
|
|
971
|
-
const Presentation visible = state
|
|
972
|
-
? state.animation->current
|
|
973
|
-
: state.latest;
|
|
1739
|
+
const Presentation visible = visiblePresentation(driverId, state);
|
|
974
1740
|
JNIEnv *env = facebook::jni::Environment::current();
|
|
975
1741
|
for (auto &existing : state.views) {
|
|
976
1742
|
if (env->IsSameObject(existing.view.get(), view.get())) {
|
|
@@ -980,9 +1746,14 @@ void registerViewAndroid(
|
|
|
980
1746
|
existing.lifecycleVisible = lifecycleVisible;
|
|
981
1747
|
deliverToView(existing, visible);
|
|
982
1748
|
reconcileViewDisplayability(driverId, state, existing);
|
|
1749
|
+
reconcileGroupReadiness(driverId);
|
|
983
1750
|
return;
|
|
984
1751
|
}
|
|
985
1752
|
}
|
|
1753
|
+
// The public controller contract permits one simultaneous host. The React
|
|
1754
|
+
// wrapper reports this as a development error; native also refuses a second
|
|
1755
|
+
// host so release behavior cannot silently fan one controller out.
|
|
1756
|
+
if (!state.views.empty()) return;
|
|
986
1757
|
ViewEntry entry{
|
|
987
1758
|
facebook::jni::make_global(view),
|
|
988
1759
|
density,
|
|
@@ -990,11 +1761,11 @@ void registerViewAndroid(
|
|
|
990
1761
|
hostHeightPx,
|
|
991
1762
|
lifecycleVisible,
|
|
992
1763
|
ViewParticipation::Deferred};
|
|
993
|
-
// `visible`
|
|
994
|
-
// registering view — the correct first frame for a latched animation.
|
|
1764
|
+
// `visible` already contains the start of a pre-ready animation.
|
|
995
1765
|
deliverToView(entry, visible);
|
|
996
1766
|
state.views.push_back(std::move(entry));
|
|
997
1767
|
reconcileViewDisplayability(driverId, state, state.views.back());
|
|
1768
|
+
reconcileGroupReadiness(driverId);
|
|
998
1769
|
}
|
|
999
1770
|
|
|
1000
1771
|
void setViewHostGeometryAndroid(
|
|
@@ -1014,12 +1785,10 @@ void setViewHostGeometryAndroid(
|
|
|
1014
1785
|
entry.hostWidthPx = hostWidthPx;
|
|
1015
1786
|
entry.hostHeightPx = hostHeightPx;
|
|
1016
1787
|
if (state.hasLatest) {
|
|
1017
|
-
deliverToView(
|
|
1018
|
-
entry,
|
|
1019
|
-
state.animation.has_value() ? state.animation->current
|
|
1020
|
-
: state.latest);
|
|
1788
|
+
deliverToView(entry, visiblePresentation(driverId, state));
|
|
1021
1789
|
}
|
|
1022
1790
|
reconcileViewDisplayability(driverId, state, entry);
|
|
1791
|
+
reconcileGroupReadiness(driverId);
|
|
1023
1792
|
return;
|
|
1024
1793
|
}
|
|
1025
1794
|
}
|
|
@@ -1037,6 +1806,7 @@ void setViewLifecycleVisibilityAndroid(
|
|
|
1037
1806
|
if (!env->IsSameObject(entry.view.get(), view.get())) continue;
|
|
1038
1807
|
entry.lifecycleVisible = lifecycleVisible;
|
|
1039
1808
|
reconcileViewDisplayability(driverId, state, entry);
|
|
1809
|
+
reconcileGroupReadiness(driverId);
|
|
1040
1810
|
return;
|
|
1041
1811
|
}
|
|
1042
1812
|
}
|
|
@@ -1048,12 +1818,9 @@ void unregisterViewAndroid(
|
|
|
1048
1818
|
if (iterator == registry().end()) return;
|
|
1049
1819
|
auto &state = iterator->second;
|
|
1050
1820
|
bool removed = false;
|
|
1051
|
-
bool removedParticipant = false;
|
|
1052
1821
|
JNIEnv *env = facebook::jni::Environment::current();
|
|
1053
1822
|
for (auto view_it = state.views.begin(); view_it != state.views.end();) {
|
|
1054
1823
|
if (env->IsSameObject(view_it->view.get(), view.get())) {
|
|
1055
|
-
removedParticipant = removedParticipant ||
|
|
1056
|
-
view_it->participation != ViewParticipation::Deferred;
|
|
1057
1824
|
view_it = state.views.erase(view_it);
|
|
1058
1825
|
removed = true;
|
|
1059
1826
|
} else {
|
|
@@ -1061,10 +1828,6 @@ void unregisterViewAndroid(
|
|
|
1061
1828
|
}
|
|
1062
1829
|
}
|
|
1063
1830
|
if (removed && state.animation.has_value()) {
|
|
1064
|
-
// Only a host that actually joined (or is unresolved after suspension)
|
|
1065
|
-
// affects completion. A detached/unsized registration that stayed deferred
|
|
1066
|
-
// never installed or rendered this animation.
|
|
1067
|
-
if (removedParticipant) state.animation->finished = false;
|
|
1068
1831
|
if (!isOnMainThread()) {
|
|
1069
1832
|
// Blast-radius reduction, NOT a synchronization fix — be clear about
|
|
1070
1833
|
// which. Both call sites (SmoothClipViewManager.onAfterUpdateTransaction
|
|
@@ -1078,9 +1841,12 @@ void unregisterViewAndroid(
|
|
|
1078
1841
|
// for the next main-thread event (attach, host geometry, destroy) to
|
|
1079
1842
|
// resolve.
|
|
1080
1843
|
} else {
|
|
1081
|
-
|
|
1844
|
+
finishIfHostUnavailable(driverId, state);
|
|
1082
1845
|
}
|
|
1083
1846
|
}
|
|
1847
|
+
if (removed && state.groupId != 0 && isOnMainThread()) {
|
|
1848
|
+
reconcileGroupReadiness(driverId);
|
|
1849
|
+
}
|
|
1084
1850
|
if (state.destroyed && state.views.empty()) {
|
|
1085
1851
|
registry().erase(iterator);
|
|
1086
1852
|
}
|