react-native-smooth-clip-view 0.2.8 → 0.3.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 +86 -21
- package/android/CMakeLists.txt +7 -0
- package/android/build.gradle +14 -2
- package/android/proguard-rules.pro +3 -3
- package/android/src/main/cpp/SmoothClipAndroid.h +10 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +896 -6
- package/android/src/main/cpp/SmoothClipRegistry.cpp +1067 -46
- package/android/src/main/cpp/SmoothClipTrace.h +25 -0
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +265 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +21 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +283 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +385 -24
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +138 -24
- package/cpp/SmoothClipAnimationCurve.h +151 -122
- package/cpp/SmoothClipRegistry.h +102 -4
- package/cpp/SmoothClipSharedGeometry.h +146 -7
- package/cpp/SmoothClipVelocityTracker.h +13 -9
- package/ios/SmoothClipGeometry.h +184 -6
- package/ios/SmoothClipRegistry.mm +1033 -25
- package/ios/SmoothClipRegistrySnapshot.h +13 -0
- package/ios/SmoothClipTurboModule.cpp +857 -2
- package/ios/SmoothClipTurboModule.h +310 -0
- package/ios/SmoothClipView.mm +714 -183
- package/ios/SmoothClipViewRegistry.h +1 -0
- 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 +22 -6
- 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 +27 -1
- package/lib/module/capabilities.js +4 -0
- package/lib/module/capabilities.js.map +1 -0
- package/lib/module/capabilities.native.js +36 -0
- package/lib/module/capabilities.native.js.map +1 -0
- package/lib/module/capabilityTypes.js +11 -0
- package/lib/module/capabilityTypes.js.map +1 -0
- package/lib/module/driverState.js +0 -28
- package/lib/module/driverState.js.map +1 -1
- package/lib/module/drivers.js +1 -281
- package/lib/module/drivers.js.map +1 -1
- package/lib/module/drivers.native.js +340 -75
- package/lib/module/drivers.native.js.map +1 -1
- package/lib/module/geometry.js +147 -17
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groupDriverTypes.js +4 -0
- package/lib/module/groupDriverTypes.js.map +1 -0
- package/lib/module/groupDrivers.android.js +4 -0
- package/lib/module/groupDrivers.android.js.map +1 -0
- package/lib/module/groupDrivers.ios.js +4 -0
- package/lib/module/groupDrivers.ios.js.map +1 -0
- package/lib/module/groupDrivers.js +4 -0
- package/lib/module/groupDrivers.js.map +1 -0
- package/lib/module/groupDrivers.native.js +799 -0
- package/lib/module/groupDrivers.native.js.map +1 -0
- package/lib/module/index.js +3 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/presentationProtocol.js +15 -0
- package/lib/module/presentationProtocol.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +25 -0
- 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 +133 -2
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +8 -0
- 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 +10 -0
- package/lib/typescript/src/capabilityTypes.d.ts.map +1 -0
- package/lib/typescript/src/driverState.d.ts +1 -6
- package/lib/typescript/src/driverState.d.ts.map +1 -1
- package/lib/typescript/src/driverTypes.d.ts +39 -11
- package/lib/typescript/src/driverTypes.d.ts.map +1 -1
- package/lib/typescript/src/drivers.d.ts +2 -4
- package/lib/typescript/src/drivers.d.ts.map +1 -1
- package/lib/typescript/src/drivers.native.d.ts.map +1 -1
- package/lib/typescript/src/geometry.d.ts +36 -2
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/groupDriverTypes.d.ts +66 -0
- package/lib/typescript/src/groupDriverTypes.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.android.d.ts +2 -0
- package/lib/typescript/src/groupDrivers.android.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.d.ts +2 -0
- package/lib/typescript/src/groupDrivers.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.ios.d.ts +2 -0
- package/lib/typescript/src/groupDrivers.ios.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.native.d.ts +4 -0
- package/lib/typescript/src/groupDrivers.native.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/presentationProtocol.d.ts +4 -0
- package/lib/typescript/src/presentationProtocol.d.ts.map +1 -0
- package/package.json +10 -11
- package/src/NativeSmoothClipModule.ts +275 -0
- package/src/SmoothClipView.ios.tsx +30 -11
- package/src/SmoothClipView.tsx +2 -76
- package/src/SmoothClipViewNativeComponent.ts +27 -1
- package/src/capabilities.native.ts +69 -0
- package/src/capabilities.ts +2 -0
- package/src/capabilityTypes.ts +17 -0
- package/src/driverState.ts +1 -41
- package/src/driverTypes.ts +56 -11
- package/src/drivers.native.ts +921 -151
- package/src/drivers.ts +2 -423
- package/src/geometry.ts +231 -27
- package/src/groupDriverTypes.ts +117 -0
- package/src/groupDrivers.android.ts +1 -0
- package/src/groupDrivers.ios.ts +1 -0
- package/src/groupDrivers.native.ts +1189 -0
- package/src/groupDrivers.ts +1 -0
- package/src/index.ts +30 -2
- package/src/presentationProtocol.ts +28 -0
- package/lib/module/SmoothClipView.web.js +0 -54
- package/lib/module/SmoothClipView.web.js.map +0 -1
- package/lib/module/index.web.js +0 -8
- package/lib/module/index.web.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/index.web.d.ts +0 -9
- package/lib/typescript/src/index.web.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/index.web.ts +0 -23
- package/src/webClipPath.ts +0 -30
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "SmoothClipAndroid.h"
|
|
2
2
|
#include "SmoothClipAnimationId.h"
|
|
3
3
|
#include "SmoothClipAnimationCurve.h"
|
|
4
|
+
#include "SmoothClipTrace.h"
|
|
4
5
|
#include "SmoothClipVelocityTracker.h"
|
|
5
6
|
|
|
6
7
|
#include <fbjni/fbjni.h>
|
|
@@ -56,14 +57,14 @@ struct ActiveAnimation {
|
|
|
56
57
|
Presentation current{{0, 0, 0, 0, 0}, 0, 0};
|
|
57
58
|
TimingAnimation timing{};
|
|
58
59
|
SpringAnimation spring{};
|
|
59
|
-
// Owns the keyframes and
|
|
60
|
-
// tangent solve happens on reset(), never per frame.
|
|
60
|
+
// Owns the segment-linear keyframes and a cursor into the active segment.
|
|
61
61
|
KeyframeCurve keyframes;
|
|
62
62
|
double durationS = 0;
|
|
63
63
|
double startedAtS = 0;
|
|
64
|
-
// Integrated spring state (
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
// Integrated spring state (the eleven continuous V2 channels; curve is
|
|
65
|
+
// categorical and remains the start presentation's curve while running).
|
|
66
|
+
Channels springPosition{};
|
|
67
|
+
Channels springVelocity{};
|
|
67
68
|
double lastFrameS = 0;
|
|
68
69
|
bool finished = true;
|
|
69
70
|
// False while the animation is latched, and held out of animatingDrivers()
|
|
@@ -103,6 +104,8 @@ struct DriverState {
|
|
|
103
104
|
// replay, hosts mounted in another subtree). The entry is erased when the
|
|
104
105
|
// last view leaves and revived by a take-ownership setPresentation.
|
|
105
106
|
bool destroyed = false;
|
|
107
|
+
// Non-zero while this driver is an immutable member of one active group.
|
|
108
|
+
int32_t groupId = 0;
|
|
106
109
|
|
|
107
110
|
// 'inherit' velocity samples; recording/coalescing/projection live in the
|
|
108
111
|
// shared cpp/SmoothClipVelocityTracker.h (behavior-paired with iOS).
|
|
@@ -114,6 +117,31 @@ struct CompletionSink {
|
|
|
114
117
|
CompletionCallback callback;
|
|
115
118
|
};
|
|
116
119
|
|
|
120
|
+
struct GroupCompletionSink {
|
|
121
|
+
const void *owner = nullptr;
|
|
122
|
+
GroupCompletionCallback callback;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
struct GroupMemberAnimation {
|
|
126
|
+
uint64_t driverId = 0;
|
|
127
|
+
ActiveAnimation animation;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
struct ActiveGroup {
|
|
131
|
+
uint64_t controllerId = 0;
|
|
132
|
+
int32_t id = 0;
|
|
133
|
+
AnimationKind kind = AnimationKind::Timing;
|
|
134
|
+
GroupSuspensionPolicy suspensionPolicy = GroupSuspensionPolicy::Pause;
|
|
135
|
+
TimingAnimation timing{};
|
|
136
|
+
SpringAnimation spring{};
|
|
137
|
+
double durationS = 0;
|
|
138
|
+
double startedAtS = 0;
|
|
139
|
+
double lastFrameS = 0;
|
|
140
|
+
bool started = false;
|
|
141
|
+
bool frameClockAnchored = false;
|
|
142
|
+
std::vector<GroupMemberAnimation> members;
|
|
143
|
+
};
|
|
144
|
+
|
|
117
145
|
std::unordered_map<uint64_t, DriverState> ®istry() {
|
|
118
146
|
static std::unordered_map<uint64_t, DriverState> value;
|
|
119
147
|
return value;
|
|
@@ -124,6 +152,21 @@ CompletionSink &completionSink() {
|
|
|
124
152
|
return sink;
|
|
125
153
|
}
|
|
126
154
|
|
|
155
|
+
GroupCompletionSink &groupCompletionSink() {
|
|
156
|
+
static GroupCompletionSink sink;
|
|
157
|
+
return sink;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
std::unordered_map<int32_t, ActiveGroup> &groupRegistry() {
|
|
161
|
+
static std::unordered_map<int32_t, ActiveGroup> value;
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
std::vector<int32_t> &animatingGroups() {
|
|
166
|
+
static std::vector<int32_t> value;
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
|
|
127
170
|
// The sink is written from the JS thread (installBindings / invalidate) and
|
|
128
171
|
// invoked on main. Invoking under the lock also keeps the owning bindings
|
|
129
172
|
// state alive for the duration of the callback.
|
|
@@ -241,7 +284,7 @@ bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
|
241
284
|
while (dt > 0) {
|
|
242
285
|
const double step = std::min(dt, kMaxStepS);
|
|
243
286
|
dt -= step;
|
|
244
|
-
for (
|
|
287
|
+
for (std::size_t index = 0; index < kChannelCount; index += 1) {
|
|
245
288
|
const double displacement =
|
|
246
289
|
animation.springPosition[index] - target[index];
|
|
247
290
|
const double acceleration =
|
|
@@ -253,7 +296,7 @@ bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
|
253
296
|
}
|
|
254
297
|
}
|
|
255
298
|
bool settled = true;
|
|
256
|
-
for (
|
|
299
|
+
for (std::size_t index = 0; index < kChannelCount; index += 1) {
|
|
257
300
|
if (!std::isfinite(animation.springPosition[index]) ||
|
|
258
301
|
!std::isfinite(animation.springVelocity[index])) {
|
|
259
302
|
// A divergent configuration must never freeze the clip: end at target.
|
|
@@ -268,7 +311,8 @@ bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
|
268
311
|
settled = false;
|
|
269
312
|
}
|
|
270
313
|
}
|
|
271
|
-
animation.current =
|
|
314
|
+
animation.current =
|
|
315
|
+
fromChannels(animation.springPosition, animation.start.clip.curve);
|
|
272
316
|
return settled || now - animation.startedAtS >= kSpringMaxDurationS;
|
|
273
317
|
}
|
|
274
318
|
|
|
@@ -295,32 +339,117 @@ void emitCompletion(uint64_t driverId, int32_t animationId, bool finished) {
|
|
|
295
339
|
}
|
|
296
340
|
}
|
|
297
341
|
|
|
342
|
+
void emitGroupCompletion(const ActiveGroup &group, bool finished) {
|
|
343
|
+
std::vector<uint64_t> driverIds;
|
|
344
|
+
driverIds.reserve(group.members.size());
|
|
345
|
+
for (const GroupMemberAnimation &member : group.members) {
|
|
346
|
+
driverIds.push_back(member.driverId);
|
|
347
|
+
}
|
|
348
|
+
std::lock_guard<std::mutex> lock(completionSinkMutex());
|
|
349
|
+
if (groupCompletionSink().callback) {
|
|
350
|
+
groupCompletionSink().callback(
|
|
351
|
+
group.controllerId, group.id, finished, std::move(driverIds));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const GroupMemberAnimation *findGroupMember(
|
|
356
|
+
int32_t groupId,
|
|
357
|
+
uint64_t driverId) {
|
|
358
|
+
const auto groupIterator = groupRegistry().find(groupId);
|
|
359
|
+
if (groupIterator == groupRegistry().end()) return nullptr;
|
|
360
|
+
for (const GroupMemberAnimation &member : groupIterator->second.members) {
|
|
361
|
+
if (member.driverId == driverId) return &member;
|
|
362
|
+
}
|
|
363
|
+
return nullptr;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
Presentation visiblePresentation(
|
|
367
|
+
uint64_t driverId,
|
|
368
|
+
const DriverState &state) {
|
|
369
|
+
if (state.groupId != 0) {
|
|
370
|
+
const GroupMemberAnimation *member =
|
|
371
|
+
findGroupMember(state.groupId, driverId);
|
|
372
|
+
if (member != nullptr) return member->animation.current;
|
|
373
|
+
}
|
|
374
|
+
return state.animation.has_value() ? state.animation->current : state.latest;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
bool needsV2Delivery(const Presentation &presentation) {
|
|
378
|
+
const double topLeft = SmoothClipResolvedRadius(
|
|
379
|
+
presentation.clip.topLeftRadius, presentation.clip.radius);
|
|
380
|
+
const double topRight = SmoothClipResolvedRadius(
|
|
381
|
+
presentation.clip.topRightRadius, presentation.clip.radius);
|
|
382
|
+
const double bottomRight = SmoothClipResolvedRadius(
|
|
383
|
+
presentation.clip.bottomRightRadius, presentation.clip.radius);
|
|
384
|
+
const double bottomLeft = SmoothClipResolvedRadius(
|
|
385
|
+
presentation.clip.bottomLeftRadius, presentation.clip.radius);
|
|
386
|
+
return presentation.clip.curve != ClipCurve::Circular ||
|
|
387
|
+
presentation.contentScale != 1.0 ||
|
|
388
|
+
topLeft != presentation.clip.radius ||
|
|
389
|
+
topRight != presentation.clip.radius ||
|
|
390
|
+
bottomRight != presentation.clip.radius ||
|
|
391
|
+
bottomLeft != presentation.clip.radius;
|
|
392
|
+
}
|
|
393
|
+
|
|
298
394
|
// Terminal per-frame delivery: scale DIP -> px and normalize against the
|
|
299
395
|
// pushed host metrics in C++, so the JNI call carries final pixel floats and
|
|
300
396
|
// the Kotlin side reduces to field stores + invalidateOutline().
|
|
301
397
|
void deliverToView(const ViewEntry &entry, const Presentation &presentation) {
|
|
398
|
+
const bool deliverV2 = needsV2Delivery(presentation);
|
|
302
399
|
if (entry.density <= 0) {
|
|
303
|
-
|
|
400
|
+
if (deliverV2) {
|
|
401
|
+
entry.view->applyClipV2(presentation);
|
|
402
|
+
} else {
|
|
403
|
+
entry.view->applyClip(presentation);
|
|
404
|
+
}
|
|
304
405
|
return;
|
|
305
406
|
}
|
|
306
407
|
const double density = entry.density;
|
|
307
408
|
NormalizedClip clip;
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
409
|
+
if (deliverV2) {
|
|
410
|
+
if (!SmoothClipNormalizeV2(
|
|
411
|
+
presentation.clip.x * density,
|
|
412
|
+
presentation.clip.y * density,
|
|
413
|
+
presentation.clip.width * density,
|
|
414
|
+
presentation.clip.height * density,
|
|
415
|
+
presentation.clip.radius * density,
|
|
416
|
+
presentation.clip.topLeftRadius * density,
|
|
417
|
+
presentation.clip.topRightRadius * density,
|
|
418
|
+
presentation.clip.bottomRightRadius * density,
|
|
419
|
+
presentation.clip.bottomLeftRadius * density,
|
|
420
|
+
presentation.clip.curve,
|
|
421
|
+
entry.hostWidthPx,
|
|
422
|
+
entry.hostHeightPx,
|
|
423
|
+
clip)) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
if (!SmoothClipNormalize(
|
|
428
|
+
presentation.clip.x * density,
|
|
429
|
+
presentation.clip.y * density,
|
|
430
|
+
presentation.clip.width * density,
|
|
431
|
+
presentation.clip.height * density,
|
|
432
|
+
presentation.clip.radius * density,
|
|
433
|
+
entry.hostWidthPx,
|
|
434
|
+
entry.hostHeightPx,
|
|
435
|
+
clip)) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
318
438
|
}
|
|
319
439
|
const double translateX = presentation.contentTranslateX * density;
|
|
320
440
|
const double translateY = presentation.contentTranslateY * density;
|
|
321
441
|
// Atomic reject, mirroring the Kotlin DIP path's all-or-nothing gate.
|
|
322
|
-
if (!std::isfinite(translateX) || !std::isfinite(translateY)
|
|
323
|
-
|
|
442
|
+
if (!std::isfinite(translateX) || !std::isfinite(translateY) ||
|
|
443
|
+
!std::isfinite(presentation.contentScale) ||
|
|
444
|
+
presentation.contentScale <= 0) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (deliverV2) {
|
|
448
|
+
entry.view->applyClipV2Px(
|
|
449
|
+
clip, translateX, translateY, presentation.contentScale);
|
|
450
|
+
} else {
|
|
451
|
+
entry.view->applyClipPx(clip, translateX, translateY);
|
|
452
|
+
}
|
|
324
453
|
}
|
|
325
454
|
|
|
326
455
|
void applyToViews(DriverState &state, const Presentation &presentation) {
|
|
@@ -368,8 +497,10 @@ Presentation prepareAnimation(
|
|
|
368
497
|
Presentation target) {
|
|
369
498
|
const Presentation visibleBefore =
|
|
370
499
|
state.animation.has_value() ? state.animation->current : state.latest;
|
|
371
|
-
|
|
372
|
-
|
|
500
|
+
// `from` is an explicit part of the replacement transaction and therefore
|
|
501
|
+
// overrides the sampled native frame even when the previous owner was a
|
|
502
|
+
// native animation/group.
|
|
503
|
+
const bool acceptsInteractiveStart = start.hasInteractiveStart;
|
|
373
504
|
finishActive(driverId, state, false);
|
|
374
505
|
const Presentation resolvedStart =
|
|
375
506
|
acceptsInteractiveStart ? start.interactiveStart : visibleBefore;
|
|
@@ -391,6 +522,77 @@ bool entryDisplayable(const ViewEntry &entry) {
|
|
|
391
522
|
entry.lifecycleVisible;
|
|
392
523
|
}
|
|
393
524
|
|
|
525
|
+
Geometry geometryInPixels(
|
|
526
|
+
const Geometry &geometry,
|
|
527
|
+
double density) {
|
|
528
|
+
Geometry result = geometry;
|
|
529
|
+
result.x *= density;
|
|
530
|
+
result.y *= density;
|
|
531
|
+
result.width *= density;
|
|
532
|
+
result.height *= density;
|
|
533
|
+
result.radius *= density;
|
|
534
|
+
result.topLeftRadius *= density;
|
|
535
|
+
result.topRightRadius *= density;
|
|
536
|
+
result.bottomRightRadius *= density;
|
|
537
|
+
result.bottomLeftRadius *= density;
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
bool presentationFitsEveryKnownHostWithoutNormalization(
|
|
542
|
+
const DriverState &state,
|
|
543
|
+
const Presentation &presentation) {
|
|
544
|
+
for (const ViewEntry &entry : state.views) {
|
|
545
|
+
// Zero host metrics are still waiting on Fabric layout and remain covered
|
|
546
|
+
// by the existing readiness latch. Once metrics exist, density is required
|
|
547
|
+
// to compare the DIP presentation against the exact pixel host bounds.
|
|
548
|
+
if (entry.hostWidthPx <= 0 || entry.hostHeightPx <= 0) continue;
|
|
549
|
+
if (entry.density <= 0 ||
|
|
550
|
+
!SmoothClipGeometryNormalizationIsIdentity(
|
|
551
|
+
geometryInPixels(presentation.clip, entry.density),
|
|
552
|
+
entry.hostWidthPx,
|
|
553
|
+
entry.hostHeightPx)) {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
bool timingPlanPreservesLinearHostNormalization(
|
|
561
|
+
const DriverState &state,
|
|
562
|
+
const Presentation &start,
|
|
563
|
+
const Presentation &target,
|
|
564
|
+
const TimingAnimation &animation) {
|
|
565
|
+
const double minimumProgress = std::min(
|
|
566
|
+
{0.0, 1.0, animation.controlPoint1Y, animation.controlPoint2Y});
|
|
567
|
+
const double maximumProgress = std::max(
|
|
568
|
+
{0.0, 1.0, animation.controlPoint1Y, animation.controlPoint2Y});
|
|
569
|
+
const Presentation minimum =
|
|
570
|
+
interpolate(start, target, minimumProgress);
|
|
571
|
+
const Presentation maximum =
|
|
572
|
+
interpolate(start, target, maximumProgress);
|
|
573
|
+
return isFiniteProtocolV2Presentation(minimum) &&
|
|
574
|
+
isFiniteProtocolV2Presentation(maximum) &&
|
|
575
|
+
presentationFitsEveryKnownHostWithoutNormalization(state, minimum) &&
|
|
576
|
+
presentationFitsEveryKnownHostWithoutNormalization(state, maximum);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
bool keyframePlanPreservesLinearHostNormalization(
|
|
580
|
+
const DriverState &state,
|
|
581
|
+
const Presentation &resolvedStart,
|
|
582
|
+
const std::vector<Keyframe> &keyframes) {
|
|
583
|
+
if (!presentationFitsEveryKnownHostWithoutNormalization(
|
|
584
|
+
state, resolvedStart)) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
for (std::size_t index = 1; index < keyframes.size(); index += 1) {
|
|
588
|
+
if (!presentationFitsEveryKnownHostWithoutNormalization(
|
|
589
|
+
state, keyframes[index].presentation)) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
|
|
394
596
|
bool anyDisplayableView(const DriverState &state) {
|
|
395
597
|
for (const ViewEntry &entry : state.views) {
|
|
396
598
|
if (entryDisplayable(entry)) return true;
|
|
@@ -398,6 +600,141 @@ bool anyDisplayableView(const DriverState &state) {
|
|
|
398
600
|
return false;
|
|
399
601
|
}
|
|
400
602
|
|
|
603
|
+
bool allGroupMembersReady(const ActiveGroup &group) {
|
|
604
|
+
for (const GroupMemberAnimation &member : group.members) {
|
|
605
|
+
const auto iterator = registry().find(member.driverId);
|
|
606
|
+
if (iterator == registry().end() || iterator->second.destroyed ||
|
|
607
|
+
!anyDisplayableView(iterator->second)) {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
void removeAnimatingGroup(int32_t groupId) {
|
|
615
|
+
auto &active = animatingGroups();
|
|
616
|
+
active.erase(std::remove(active.begin(), active.end(), groupId), active.end());
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
void applyGroupCurrent(const ActiveGroup &group) {
|
|
620
|
+
for (const GroupMemberAnimation &member : group.members) {
|
|
621
|
+
const auto iterator = registry().find(member.driverId);
|
|
622
|
+
if (iterator != registry().end()) {
|
|
623
|
+
applyToViews(iterator->second, member.animation.current);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
std::vector<DriverSnapshot> finishGroupImpl(
|
|
629
|
+
int32_t groupId,
|
|
630
|
+
bool useTarget,
|
|
631
|
+
bool finished) {
|
|
632
|
+
auto groupIterator = groupRegistry().find(groupId);
|
|
633
|
+
if (groupIterator == groupRegistry().end()) return {};
|
|
634
|
+
ActiveGroup group = std::move(groupIterator->second);
|
|
635
|
+
groupRegistry().erase(groupIterator);
|
|
636
|
+
removeAnimatingGroup(groupId);
|
|
637
|
+
|
|
638
|
+
std::vector<DriverSnapshot> snapshots;
|
|
639
|
+
snapshots.reserve(group.members.size());
|
|
640
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
641
|
+
auto stateIterator = registry().find(member.driverId);
|
|
642
|
+
if (stateIterator == registry().end()) {
|
|
643
|
+
snapshots.push_back({member.driverId, unavailablePresentation(), false});
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
DriverState &state = stateIterator->second;
|
|
647
|
+
const Presentation result =
|
|
648
|
+
useTarget ? member.animation.target : member.animation.current;
|
|
649
|
+
state.groupId = 0;
|
|
650
|
+
state.latest = result;
|
|
651
|
+
state.hasLatest = true;
|
|
652
|
+
state.ownership = Ownership::Interactive;
|
|
653
|
+
applyToViews(state, result);
|
|
654
|
+
snapshots.push_back(
|
|
655
|
+
{member.driverId, result, !state.destroyed && anyDisplayableView(state)});
|
|
656
|
+
}
|
|
657
|
+
emitGroupCompletion(group, finished);
|
|
658
|
+
return snapshots;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
void startLatchedGroup(ActiveGroup &group) {
|
|
662
|
+
const double now = nowSeconds();
|
|
663
|
+
group.started = true;
|
|
664
|
+
group.startedAtS = now;
|
|
665
|
+
group.lastFrameS = now;
|
|
666
|
+
group.frameClockAnchored = false;
|
|
667
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
668
|
+
member.animation.startedAtS = now;
|
|
669
|
+
member.animation.lastFrameS = now;
|
|
670
|
+
member.animation.frameClockAnchored = false;
|
|
671
|
+
}
|
|
672
|
+
auto &active = animatingGroups();
|
|
673
|
+
if (std::find(active.begin(), active.end(), group.id) == active.end()) {
|
|
674
|
+
active.push_back(group.id);
|
|
675
|
+
}
|
|
676
|
+
applyGroupCurrent(group);
|
|
677
|
+
scheduleFrame();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
void pauseGroup(ActiveGroup &group) {
|
|
681
|
+
if (group.kind == AnimationKind::Timing) {
|
|
682
|
+
const double rawProgress = timingFraction(
|
|
683
|
+
group.lastFrameS, group.startedAtS, group.durationS);
|
|
684
|
+
const TimingRemainder remainder = timingRemainder(group.timing, rawProgress);
|
|
685
|
+
group.timing = remainder.animation;
|
|
686
|
+
group.durationS = remainder.animation.durationMs / 1000.0;
|
|
687
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
688
|
+
member.animation.start = member.animation.current;
|
|
689
|
+
member.animation.timing = remainder.animation;
|
|
690
|
+
}
|
|
691
|
+
} else if (group.kind == AnimationKind::Keyframes) {
|
|
692
|
+
double remainingDurationMs = 0;
|
|
693
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
694
|
+
const KeyframeContinuation continuation = keyframeContinuationAtFrame(
|
|
695
|
+
member.animation.keyframes.frames(),
|
|
696
|
+
member.animation.current,
|
|
697
|
+
member.animation.target,
|
|
698
|
+
group.lastFrameS,
|
|
699
|
+
group.startedAtS,
|
|
700
|
+
group.durationS);
|
|
701
|
+
member.animation.start = continuation.start;
|
|
702
|
+
member.animation.current = continuation.start;
|
|
703
|
+
member.animation.keyframes.reset(continuation.frames);
|
|
704
|
+
remainingDurationMs = continuation.durationMs;
|
|
705
|
+
}
|
|
706
|
+
group.durationS = remainingDurationMs / 1000.0;
|
|
707
|
+
}
|
|
708
|
+
group.started = false;
|
|
709
|
+
group.frameClockAnchored = false;
|
|
710
|
+
removeAnimatingGroup(group.id);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
void reconcileGroupReadiness(uint64_t driverId) {
|
|
714
|
+
const auto stateIterator = registry().find(driverId);
|
|
715
|
+
if (stateIterator == registry().end() || stateIterator->second.groupId == 0) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const int32_t groupId = stateIterator->second.groupId;
|
|
719
|
+
auto groupIterator = groupRegistry().find(groupId);
|
|
720
|
+
if (groupIterator == groupRegistry().end()) {
|
|
721
|
+
stateIterator->second.groupId = 0;
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
ActiveGroup &group = groupIterator->second;
|
|
725
|
+
const bool ready = allGroupMembersReady(group);
|
|
726
|
+
if (!group.started) {
|
|
727
|
+
if (ready) startLatchedGroup(group);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
if (ready) return;
|
|
731
|
+
if (group.suspensionPolicy == GroupSuspensionPolicy::Finish) {
|
|
732
|
+
finishGroupImpl(groupId, true, true);
|
|
733
|
+
} else {
|
|
734
|
+
pauseGroup(group);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
401
738
|
// Starts a latched animation: rebases the clock so no progress was burned
|
|
402
739
|
// while no view could display, then joins the choreographer loop.
|
|
403
740
|
void startLatchedAnimation(uint64_t driverId, DriverState &state) {
|
|
@@ -614,7 +951,56 @@ void advance(uint64_t driverId, DriverState &state, double now) {
|
|
|
614
951
|
}
|
|
615
952
|
}
|
|
616
953
|
|
|
954
|
+
void advanceGroup(int32_t groupId, ActiveGroup &group, double now) {
|
|
955
|
+
if (!group.frameClockAnchored) {
|
|
956
|
+
group.startedAtS = anchorStartTime(group.startedAtS, now);
|
|
957
|
+
group.lastFrameS = group.startedAtS;
|
|
958
|
+
group.frameClockAnchored = true;
|
|
959
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
960
|
+
member.animation.startedAtS = group.startedAtS;
|
|
961
|
+
member.animation.lastFrameS = group.startedAtS;
|
|
962
|
+
member.animation.frameClockAnchored = true;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
bool done = false;
|
|
967
|
+
if (group.kind == AnimationKind::Spring) {
|
|
968
|
+
done = true;
|
|
969
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
970
|
+
if (!advanceSpring(member.animation, now)) done = false;
|
|
971
|
+
}
|
|
972
|
+
} else {
|
|
973
|
+
group.lastFrameS = now;
|
|
974
|
+
const double fraction =
|
|
975
|
+
timingFraction(now, group.startedAtS, group.durationS);
|
|
976
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
977
|
+
member.animation.lastFrameS = now;
|
|
978
|
+
if (group.kind == AnimationKind::Keyframes) {
|
|
979
|
+
member.animation.current =
|
|
980
|
+
member.animation.keyframes.evaluate(fraction);
|
|
981
|
+
} else {
|
|
982
|
+
const double eased = cubicBezier(
|
|
983
|
+
group.timing.controlPoint1X,
|
|
984
|
+
group.timing.controlPoint1Y,
|
|
985
|
+
group.timing.controlPoint2X,
|
|
986
|
+
group.timing.controlPoint2Y,
|
|
987
|
+
fraction);
|
|
988
|
+
member.animation.current = interpolate(
|
|
989
|
+
member.animation.start, member.animation.target, eased);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
done = fraction >= 1.0;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (done) {
|
|
996
|
+
finishGroupImpl(groupId, true, true);
|
|
997
|
+
} else {
|
|
998
|
+
applyGroupCurrent(group);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
617
1002
|
void onFrameImpl(double now) {
|
|
1003
|
+
SMOOTH_CLIP_TRACE("SmoothClip.frame");
|
|
618
1004
|
gFrameScheduled = false;
|
|
619
1005
|
// Snapshot: advance() clears live-list entries on completion. advance()
|
|
620
1006
|
// never erases registry entries and never starts animations (completion
|
|
@@ -632,7 +1018,14 @@ void onFrameImpl(double now) {
|
|
|
632
1018
|
}
|
|
633
1019
|
advance(driverId, iterator->second, now);
|
|
634
1020
|
}
|
|
635
|
-
|
|
1021
|
+
static std::vector<int32_t> groupScratch;
|
|
1022
|
+
groupScratch = animatingGroups();
|
|
1023
|
+
for (const int32_t groupId : groupScratch) {
|
|
1024
|
+
auto iterator = groupRegistry().find(groupId);
|
|
1025
|
+
if (iterator == groupRegistry().end() || !iterator->second.started) continue;
|
|
1026
|
+
advanceGroup(groupId, iterator->second, now);
|
|
1027
|
+
}
|
|
1028
|
+
if (!animatingDrivers().empty() || !animatingGroups().empty()) scheduleFrame();
|
|
636
1029
|
}
|
|
637
1030
|
|
|
638
1031
|
} // namespace
|
|
@@ -671,12 +1064,415 @@ void clearCompletionCallback(const void *owner) {
|
|
|
671
1064
|
completionSink().callback = nullptr;
|
|
672
1065
|
}
|
|
673
1066
|
|
|
1067
|
+
void setGroupCompletionCallback(
|
|
1068
|
+
const void *owner,
|
|
1069
|
+
GroupCompletionCallback callback) {
|
|
1070
|
+
std::lock_guard<std::mutex> lock(completionSinkMutex());
|
|
1071
|
+
groupCompletionSink().owner = owner;
|
|
1072
|
+
groupCompletionSink().callback = std::move(callback);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
void clearGroupCompletionCallback(const void *owner) {
|
|
1076
|
+
std::lock_guard<std::mutex> lock(completionSinkMutex());
|
|
1077
|
+
if (groupCompletionSink().owner != owner) return;
|
|
1078
|
+
groupCompletionSink().owner = nullptr;
|
|
1079
|
+
groupCompletionSink().callback = nullptr;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
Presentation snapshotCurrent(uint64_t driverId) {
|
|
1083
|
+
if (!isOnMainThread()) return unavailablePresentation();
|
|
1084
|
+
const auto iterator = registry().find(driverId);
|
|
1085
|
+
if (iterator == registry().end() || iterator->second.destroyed ||
|
|
1086
|
+
!iterator->second.hasLatest) {
|
|
1087
|
+
return unavailablePresentation();
|
|
1088
|
+
}
|
|
1089
|
+
return visiblePresentation(driverId, iterator->second);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
Presentation snapshotCurrentAndroid(uint64_t driverId) {
|
|
1093
|
+
return snapshotCurrent(driverId);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
std::vector<DriverSnapshot> snapshotGroup(
|
|
1097
|
+
const std::vector<uint64_t> &driverIds) {
|
|
1098
|
+
std::vector<DriverSnapshot> snapshots;
|
|
1099
|
+
snapshots.reserve(driverIds.size());
|
|
1100
|
+
if (!isOnMainThread()) return snapshots;
|
|
1101
|
+
for (const uint64_t driverId : driverIds) {
|
|
1102
|
+
const auto iterator = registry().find(driverId);
|
|
1103
|
+
if (iterator == registry().end() || iterator->second.destroyed ||
|
|
1104
|
+
!iterator->second.hasLatest) {
|
|
1105
|
+
snapshots.push_back({driverId, unavailablePresentation(), false});
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
const DriverState &state = iterator->second;
|
|
1109
|
+
snapshots.push_back(
|
|
1110
|
+
{driverId, visiblePresentation(driverId, state),
|
|
1111
|
+
anyDisplayableView(state)});
|
|
1112
|
+
}
|
|
1113
|
+
return snapshots;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
std::vector<DriverSnapshot> beginGroupInteraction(
|
|
1117
|
+
const std::vector<uint64_t> &driverIds) {
|
|
1118
|
+
if (!isOnMainThread()) return {};
|
|
1119
|
+
std::vector<uint64_t> unique;
|
|
1120
|
+
unique.reserve(driverIds.size());
|
|
1121
|
+
for (const uint64_t driverId : driverIds) {
|
|
1122
|
+
if (driverId == 0 ||
|
|
1123
|
+
std::find(unique.begin(), unique.end(), driverId) != unique.end()) {
|
|
1124
|
+
return {};
|
|
1125
|
+
}
|
|
1126
|
+
const auto iterator = registry().find(driverId);
|
|
1127
|
+
if (iterator == registry().end() || iterator->second.destroyed ||
|
|
1128
|
+
!iterator->second.hasLatest) {
|
|
1129
|
+
return snapshotGroup(driverIds);
|
|
1130
|
+
}
|
|
1131
|
+
unique.push_back(driverId);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
std::vector<int32_t> groupsToCancel;
|
|
1135
|
+
for (const uint64_t driverId : driverIds) {
|
|
1136
|
+
const int32_t groupId = registry().at(driverId).groupId;
|
|
1137
|
+
if (groupId != 0 &&
|
|
1138
|
+
std::find(groupsToCancel.begin(), groupsToCancel.end(), groupId) ==
|
|
1139
|
+
groupsToCancel.end()) {
|
|
1140
|
+
groupsToCancel.push_back(groupId);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
for (const int32_t groupId : groupsToCancel) {
|
|
1144
|
+
finishGroupImpl(groupId, false, false);
|
|
1145
|
+
}
|
|
1146
|
+
for (const uint64_t driverId : driverIds) {
|
|
1147
|
+
DriverState &state = registry().at(driverId);
|
|
1148
|
+
if (state.animation.has_value()) {
|
|
1149
|
+
const int32_t animationId = state.animation->id;
|
|
1150
|
+
const Presentation current = state.animation->current;
|
|
1151
|
+
clearActiveAnimation(driverId, state);
|
|
1152
|
+
state.latest = current;
|
|
1153
|
+
state.hasLatest = true;
|
|
1154
|
+
applyToViews(state, current);
|
|
1155
|
+
emitCompletion(driverId, animationId, false);
|
|
1156
|
+
}
|
|
1157
|
+
state.ownership = Ownership::Interactive;
|
|
1158
|
+
}
|
|
1159
|
+
return snapshotGroup(driverIds);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
bool setPresentationBatch(const std::vector<BatchEntry> &entries) {
|
|
1163
|
+
if (!isOnMainThread() || entries.empty()) return false;
|
|
1164
|
+
std::vector<uint64_t> unique;
|
|
1165
|
+
std::vector<int32_t> groupsToCancel;
|
|
1166
|
+
unique.reserve(entries.size());
|
|
1167
|
+
for (const BatchEntry &entry : entries) {
|
|
1168
|
+
if (entry.driverId == 0 ||
|
|
1169
|
+
std::find(unique.begin(), unique.end(), entry.driverId) != unique.end()) {
|
|
1170
|
+
return false;
|
|
1171
|
+
}
|
|
1172
|
+
const auto iterator = registry().find(entry.driverId);
|
|
1173
|
+
if (iterator == registry().end() || iterator->second.destroyed ||
|
|
1174
|
+
!isFiniteProtocolV2Presentation(entry.presentation)) {
|
|
1175
|
+
return false;
|
|
1176
|
+
}
|
|
1177
|
+
if (iterator != registry().end() && iterator->second.groupId != 0 &&
|
|
1178
|
+
std::find(
|
|
1179
|
+
groupsToCancel.begin(),
|
|
1180
|
+
groupsToCancel.end(),
|
|
1181
|
+
iterator->second.groupId) == groupsToCancel.end()) {
|
|
1182
|
+
groupsToCancel.push_back(iterator->second.groupId);
|
|
1183
|
+
}
|
|
1184
|
+
unique.push_back(entry.driverId);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// Replacement is all-or-nothing: validation above is deliberately complete
|
|
1188
|
+
// before any old group is dissolved. An overlap dissolves the whole immutable
|
|
1189
|
+
// old group (including members absent from this batch) exactly once.
|
|
1190
|
+
for (const int32_t groupId : groupsToCancel) {
|
|
1191
|
+
finishGroupImpl(groupId, false, false);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
const double now = nowSeconds();
|
|
1195
|
+
for (const BatchEntry &entry : entries) {
|
|
1196
|
+
DriverState &state = registry().at(entry.driverId);
|
|
1197
|
+
state.destroyed = false;
|
|
1198
|
+
finishActive(entry.driverId, state, false);
|
|
1199
|
+
state.latest = entry.presentation;
|
|
1200
|
+
state.hasLatest = true;
|
|
1201
|
+
state.ownership = Ownership::Interactive;
|
|
1202
|
+
recordVelocitySample(state.samples, toChannels(entry.presentation), now);
|
|
1203
|
+
}
|
|
1204
|
+
for (const BatchEntry &entry : entries) {
|
|
1205
|
+
applyToViews(registry().at(entry.driverId), entry.presentation);
|
|
1206
|
+
}
|
|
1207
|
+
return true;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
namespace {
|
|
1211
|
+
|
|
1212
|
+
int32_t startGroupCommon(
|
|
1213
|
+
uint64_t controllerId,
|
|
1214
|
+
std::vector<GroupMotionEntry> entries,
|
|
1215
|
+
AnimationKind kind,
|
|
1216
|
+
TimingAnimation timing,
|
|
1217
|
+
SpringAnimation spring,
|
|
1218
|
+
double durationMs,
|
|
1219
|
+
int32_t reduceMotion,
|
|
1220
|
+
GroupSuspensionPolicy suspensionPolicy,
|
|
1221
|
+
double startedAtHintS) {
|
|
1222
|
+
if (!isOnMainThread() || controllerId == 0 || entries.empty() ||
|
|
1223
|
+
(suspensionPolicy != GroupSuspensionPolicy::Pause &&
|
|
1224
|
+
suspensionPolicy != GroupSuspensionPolicy::Finish)) {
|
|
1225
|
+
return 0;
|
|
1226
|
+
}
|
|
1227
|
+
if (kind == AnimationKind::Timing &&
|
|
1228
|
+
!isValidProtocolV2Timing(timing)) {
|
|
1229
|
+
return 0;
|
|
1230
|
+
}
|
|
1231
|
+
if (kind == AnimationKind::Spring &&
|
|
1232
|
+
!isValidProtocolV2Spring(spring)) {
|
|
1233
|
+
return 0;
|
|
1234
|
+
}
|
|
1235
|
+
if (kind == AnimationKind::Keyframes &&
|
|
1236
|
+
(!std::isfinite(durationMs) || durationMs < 0 ||
|
|
1237
|
+
!isValidReduceMotionCode(reduceMotion))) {
|
|
1238
|
+
return 0;
|
|
1239
|
+
}
|
|
1240
|
+
const bool reduce = shouldReduceMotion(reduceMotion) ||
|
|
1241
|
+
(kind != AnimationKind::Spring && durationMs <= 0);
|
|
1242
|
+
std::vector<uint64_t> unique;
|
|
1243
|
+
std::vector<int32_t> groupsToCancel;
|
|
1244
|
+
std::vector<Presentation> resolvedStarts;
|
|
1245
|
+
unique.reserve(entries.size());
|
|
1246
|
+
resolvedStarts.reserve(entries.size());
|
|
1247
|
+
for (const GroupMotionEntry &entry : entries) {
|
|
1248
|
+
if (entry.driverId == 0 ||
|
|
1249
|
+
std::find(unique.begin(), unique.end(), entry.driverId) != unique.end()) {
|
|
1250
|
+
return 0;
|
|
1251
|
+
}
|
|
1252
|
+
const auto iterator = registry().find(entry.driverId);
|
|
1253
|
+
if ((iterator == registry().end() || iterator->second.destroyed ||
|
|
1254
|
+
!iterator->second.hasLatest) && !entry.hasFrom) {
|
|
1255
|
+
return 0;
|
|
1256
|
+
}
|
|
1257
|
+
if (iterator != registry().end() && iterator->second.groupId != 0 &&
|
|
1258
|
+
std::find(
|
|
1259
|
+
groupsToCancel.begin(),
|
|
1260
|
+
groupsToCancel.end(),
|
|
1261
|
+
iterator->second.groupId) == groupsToCancel.end()) {
|
|
1262
|
+
groupsToCancel.push_back(iterator->second.groupId);
|
|
1263
|
+
}
|
|
1264
|
+
const Presentation resolvedStart = entry.hasFrom
|
|
1265
|
+
? entry.from
|
|
1266
|
+
: visiblePresentation(entry.driverId, iterator->second);
|
|
1267
|
+
if (!isFiniteProtocolV2Presentation(resolvedStart) ||
|
|
1268
|
+
!isFiniteProtocolV2Presentation(entry.target) ||
|
|
1269
|
+
resolvedStart.clip.curve != entry.target.clip.curve) {
|
|
1270
|
+
return 0;
|
|
1271
|
+
}
|
|
1272
|
+
if (kind == AnimationKind::Spring &&
|
|
1273
|
+
!protocolV2SpringScaleIsProvablyPositive(
|
|
1274
|
+
resolvedStart, entry.target, spring)) {
|
|
1275
|
+
return 0;
|
|
1276
|
+
}
|
|
1277
|
+
if (kind == AnimationKind::Keyframes) {
|
|
1278
|
+
if (!isValidProtocolV2Keyframes(
|
|
1279
|
+
entry.keyframes,
|
|
1280
|
+
resolvedStart,
|
|
1281
|
+
entry.target,
|
|
1282
|
+
entry.hasFrom)) {
|
|
1283
|
+
return 0;
|
|
1284
|
+
}
|
|
1285
|
+
} else if (!entry.keyframes.empty()) {
|
|
1286
|
+
return 0;
|
|
1287
|
+
}
|
|
1288
|
+
if (!reduce && iterator != registry().end()) {
|
|
1289
|
+
if (kind == AnimationKind::Timing &&
|
|
1290
|
+
!timingPlanPreservesLinearHostNormalization(
|
|
1291
|
+
iterator->second, resolvedStart, entry.target, timing)) {
|
|
1292
|
+
return 0;
|
|
1293
|
+
}
|
|
1294
|
+
if (kind == AnimationKind::Keyframes &&
|
|
1295
|
+
!keyframePlanPreservesLinearHostNormalization(
|
|
1296
|
+
iterator->second, resolvedStart, entry.keyframes)) {
|
|
1297
|
+
return 0;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
resolvedStarts.push_back(resolvedStart);
|
|
1301
|
+
unique.push_back(entry.driverId);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// Capture every implicit start above, while overlapped groups still own
|
|
1305
|
+
// their current frames. Only after full preflight may replacement dissolve
|
|
1306
|
+
// those immutable groups and emit one unfinished completion for each.
|
|
1307
|
+
for (const int32_t groupId : groupsToCancel) {
|
|
1308
|
+
finishGroupImpl(groupId, false, false);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
ActiveGroup group;
|
|
1312
|
+
group.controllerId = controllerId;
|
|
1313
|
+
group.id = allocateAnimationId();
|
|
1314
|
+
group.kind = kind;
|
|
1315
|
+
group.suspensionPolicy = suspensionPolicy;
|
|
1316
|
+
group.timing = timing;
|
|
1317
|
+
group.spring = spring;
|
|
1318
|
+
group.durationS = durationMs / 1000.0;
|
|
1319
|
+
group.members.reserve(entries.size());
|
|
1320
|
+
const double wallNow = nowSeconds();
|
|
1321
|
+
|
|
1322
|
+
for (std::size_t entryIndex = 0; entryIndex < entries.size(); entryIndex += 1) {
|
|
1323
|
+
GroupMotionEntry &entry = entries[entryIndex];
|
|
1324
|
+
DriverState &state = registry()[entry.driverId];
|
|
1325
|
+
const Presentation resolvedStart = resolvedStarts[entryIndex];
|
|
1326
|
+
finishActive(entry.driverId, state, false);
|
|
1327
|
+
state.destroyed = false;
|
|
1328
|
+
state.latest = entry.target;
|
|
1329
|
+
state.hasLatest = true;
|
|
1330
|
+
state.ownership = Ownership::Native;
|
|
1331
|
+
state.groupId = group.id;
|
|
1332
|
+
|
|
1333
|
+
GroupMemberAnimation member;
|
|
1334
|
+
member.driverId = entry.driverId;
|
|
1335
|
+
member.animation.id = group.id;
|
|
1336
|
+
member.animation.kind = kind;
|
|
1337
|
+
member.animation.start = resolvedStart;
|
|
1338
|
+
member.animation.target = entry.target;
|
|
1339
|
+
member.animation.current = resolvedStart;
|
|
1340
|
+
member.animation.timing = timing;
|
|
1341
|
+
member.animation.spring = spring;
|
|
1342
|
+
member.animation.durationS = group.durationS;
|
|
1343
|
+
if (kind == AnimationKind::Keyframes) {
|
|
1344
|
+
if (!entry.hasFrom) {
|
|
1345
|
+
entry.keyframes.front().presentation = resolvedStart;
|
|
1346
|
+
}
|
|
1347
|
+
member.animation.keyframes.reset(std::move(entry.keyframes));
|
|
1348
|
+
} else if (kind == AnimationKind::Spring) {
|
|
1349
|
+
const double velocity = spring.inheritVelocity
|
|
1350
|
+
? inheritedVelocity(state.samples, toChannels(entry.target), wallNow)
|
|
1351
|
+
: spring.initialVelocity;
|
|
1352
|
+
member.animation.spring.initialVelocity = velocity;
|
|
1353
|
+
member.animation.spring.inheritVelocity = false;
|
|
1354
|
+
const Channels startChannels = toChannels(resolvedStart);
|
|
1355
|
+
const Channels targetChannels = toChannels(entry.target);
|
|
1356
|
+
for (std::size_t index = 0; index < kChannelCount; index += 1) {
|
|
1357
|
+
member.animation.springPosition[index] = startChannels[index];
|
|
1358
|
+
member.animation.springVelocity[index] =
|
|
1359
|
+
velocity * (targetChannels[index] - startChannels[index]);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
group.members.push_back(std::move(member));
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// Apply every explicit start after the whole participant set has passed
|
|
1366
|
+
// preflight and acquired the new immutable group id. This remains one
|
|
1367
|
+
// synchronous main-thread transaction even when the initial readiness
|
|
1368
|
+
// barrier is latched; ready hosts must not keep displaying their old frame
|
|
1369
|
+
// while snapshots already report `from`.
|
|
1370
|
+
for (std::size_t entryIndex = 0; entryIndex < entries.size(); entryIndex += 1) {
|
|
1371
|
+
if (!entries[entryIndex].hasFrom) continue;
|
|
1372
|
+
applyToViews(
|
|
1373
|
+
registry().at(entries[entryIndex].driverId),
|
|
1374
|
+
resolvedStarts[entryIndex]);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
const int32_t groupId = group.id;
|
|
1378
|
+
const StartStamp stamp = resolveStartStamp(startedAtHintS, wallNow);
|
|
1379
|
+
group.startedAtS = stamp.startedAtS;
|
|
1380
|
+
group.lastFrameS = stamp.startedAtS;
|
|
1381
|
+
group.frameClockAnchored = stamp.frameClockAnchored;
|
|
1382
|
+
for (GroupMemberAnimation &member : group.members) {
|
|
1383
|
+
member.animation.startedAtS = stamp.startedAtS;
|
|
1384
|
+
member.animation.lastFrameS = stamp.startedAtS;
|
|
1385
|
+
member.animation.frameClockAnchored = stamp.frameClockAnchored;
|
|
1386
|
+
}
|
|
1387
|
+
group.started = allGroupMembersReady(group);
|
|
1388
|
+
groupRegistry().emplace(groupId, std::move(group));
|
|
1389
|
+
if (reduce) {
|
|
1390
|
+
finishGroupImpl(groupId, true, true);
|
|
1391
|
+
return groupId;
|
|
1392
|
+
}
|
|
1393
|
+
ActiveGroup &stored = groupRegistry().at(groupId);
|
|
1394
|
+
if (stored.started) {
|
|
1395
|
+
animatingGroups().push_back(groupId);
|
|
1396
|
+
applyGroupCurrent(stored);
|
|
1397
|
+
scheduleFrame();
|
|
1398
|
+
}
|
|
1399
|
+
return groupId;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
} // namespace
|
|
1403
|
+
|
|
1404
|
+
int32_t animateTimingGroup(
|
|
1405
|
+
uint64_t controllerId,
|
|
1406
|
+
std::vector<GroupMotionEntry> entries,
|
|
1407
|
+
TimingAnimation animation,
|
|
1408
|
+
GroupSuspensionPolicy suspensionPolicy,
|
|
1409
|
+
double startedAtHintS) {
|
|
1410
|
+
return startGroupCommon(
|
|
1411
|
+
controllerId,
|
|
1412
|
+
std::move(entries),
|
|
1413
|
+
AnimationKind::Timing,
|
|
1414
|
+
animation,
|
|
1415
|
+
{},
|
|
1416
|
+
animation.durationMs,
|
|
1417
|
+
animation.reduceMotion,
|
|
1418
|
+
suspensionPolicy,
|
|
1419
|
+
startedAtHintS);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
int32_t animateSpringGroup(
|
|
1423
|
+
uint64_t controllerId,
|
|
1424
|
+
std::vector<GroupMotionEntry> entries,
|
|
1425
|
+
SpringAnimation animation,
|
|
1426
|
+
GroupSuspensionPolicy suspensionPolicy,
|
|
1427
|
+
double startedAtHintS) {
|
|
1428
|
+
return startGroupCommon(
|
|
1429
|
+
controllerId,
|
|
1430
|
+
std::move(entries),
|
|
1431
|
+
AnimationKind::Spring,
|
|
1432
|
+
{},
|
|
1433
|
+
animation,
|
|
1434
|
+
kSpringMaxDurationS * 1000.0,
|
|
1435
|
+
animation.reduceMotion,
|
|
1436
|
+
suspensionPolicy,
|
|
1437
|
+
startedAtHintS);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
int32_t animateKeyframesGroup(
|
|
1441
|
+
uint64_t controllerId,
|
|
1442
|
+
std::vector<GroupMotionEntry> entries,
|
|
1443
|
+
double durationMs,
|
|
1444
|
+
int32_t reduceMotion,
|
|
1445
|
+
GroupSuspensionPolicy suspensionPolicy,
|
|
1446
|
+
double startedAtHintS) {
|
|
1447
|
+
return startGroupCommon(
|
|
1448
|
+
controllerId,
|
|
1449
|
+
std::move(entries),
|
|
1450
|
+
AnimationKind::Keyframes,
|
|
1451
|
+
{},
|
|
1452
|
+
{},
|
|
1453
|
+
durationMs,
|
|
1454
|
+
reduceMotion,
|
|
1455
|
+
suspensionPolicy,
|
|
1456
|
+
startedAtHintS);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
std::vector<DriverSnapshot> cancelAnimationGroup(
|
|
1460
|
+
int32_t groupId,
|
|
1461
|
+
GroupCancelBehavior behavior) {
|
|
1462
|
+
if (!isOnMainThread()) return {};
|
|
1463
|
+
const bool finish = behavior == GroupCancelBehavior::Finish;
|
|
1464
|
+
return finishGroupImpl(
|
|
1465
|
+
groupId, finish, finish);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
674
1468
|
void setPresentation(
|
|
675
1469
|
uint64_t driverId,
|
|
676
1470
|
Presentation presentation,
|
|
677
1471
|
bool takeOwnership,
|
|
678
|
-
bool overridePendingAnimation
|
|
1472
|
+
bool overridePendingAnimation,
|
|
1473
|
+
bool recordVelocity) {
|
|
679
1474
|
if (!isOnMainThread()) return;
|
|
1475
|
+
SMOOTH_CLIP_TRACE("SmoothClip.setPresentation");
|
|
680
1476
|
auto iterator = registry().find(driverId);
|
|
681
1477
|
if (!takeOwnership) {
|
|
682
1478
|
if (iterator == registry().end() || iterator->second.destroyed) {
|
|
@@ -688,6 +1484,10 @@ void setPresentation(
|
|
|
688
1484
|
iterator = registry().try_emplace(driverId).first;
|
|
689
1485
|
}
|
|
690
1486
|
auto &state = iterator->second;
|
|
1487
|
+
if (state.groupId != 0) {
|
|
1488
|
+
if (!takeOwnership) return;
|
|
1489
|
+
finishGroupImpl(state.groupId, false, false);
|
|
1490
|
+
}
|
|
691
1491
|
if (!takeOwnership && state.ownership != Ownership::Interactive) return;
|
|
692
1492
|
if (takeOwnership) {
|
|
693
1493
|
state.destroyed = false;
|
|
@@ -703,7 +1503,12 @@ void setPresentation(
|
|
|
703
1503
|
state.latest = presentation;
|
|
704
1504
|
state.hasLatest = true;
|
|
705
1505
|
state.ownership = Ownership::Interactive;
|
|
706
|
-
|
|
1506
|
+
if (recordVelocity) {
|
|
1507
|
+
recordVelocitySample(state.samples, toChannels(presentation), nowSeconds());
|
|
1508
|
+
} else if (state.samples.hasLatest) {
|
|
1509
|
+
// Untracked movement invalidates the recorded velocity pair.
|
|
1510
|
+
clearVelocitySamples(state.samples);
|
|
1511
|
+
}
|
|
707
1512
|
applyToViews(state, presentation);
|
|
708
1513
|
}
|
|
709
1514
|
|
|
@@ -712,6 +1517,9 @@ Presentation beginInteraction(uint64_t driverId) {
|
|
|
712
1517
|
auto iterator = registry().find(driverId);
|
|
713
1518
|
if (iterator == registry().end()) return unavailablePresentation();
|
|
714
1519
|
auto &state = iterator->second;
|
|
1520
|
+
if (state.groupId != 0) {
|
|
1521
|
+
finishGroupImpl(state.groupId, false, false);
|
|
1522
|
+
}
|
|
715
1523
|
state.destroyed = false;
|
|
716
1524
|
if (!state.animation.has_value()) {
|
|
717
1525
|
state.ownership = Ownership::Interactive;
|
|
@@ -734,21 +1542,62 @@ int32_t animateTiming(
|
|
|
734
1542
|
uint64_t driverId,
|
|
735
1543
|
AnimationStart start,
|
|
736
1544
|
Presentation presentation,
|
|
737
|
-
TimingAnimation animation
|
|
738
|
-
|
|
1545
|
+
TimingAnimation animation,
|
|
1546
|
+
AnimationValidationMode validationMode) {
|
|
1547
|
+
const bool strictV2 =
|
|
1548
|
+
validationMode == AnimationValidationMode::ProtocolV2;
|
|
1549
|
+
const bool validAnimation = strictV2
|
|
1550
|
+
? isValidProtocolV2Timing(animation)
|
|
1551
|
+
: std::isfinite(animation.durationMs) &&
|
|
1552
|
+
std::isfinite(animation.controlPoint1X) &&
|
|
1553
|
+
std::isfinite(animation.controlPoint1Y) &&
|
|
1554
|
+
std::isfinite(animation.controlPoint2X) &&
|
|
1555
|
+
std::isfinite(animation.controlPoint2Y) &&
|
|
1556
|
+
isValidReduceMotionCode(animation.reduceMotion);
|
|
1557
|
+
if (!strictV2 && validAnimation) {
|
|
1558
|
+
animation.durationMs = std::max(0.0, animation.durationMs);
|
|
1559
|
+
}
|
|
1560
|
+
if (!isOnMainThread() || driverId == 0 ||
|
|
1561
|
+
!isFiniteProtocolV2Presentation(presentation) || !validAnimation) {
|
|
1562
|
+
return 0;
|
|
1563
|
+
}
|
|
739
1564
|
auto iterator = registry().find(driverId);
|
|
1565
|
+
Presentation preflightStart;
|
|
1566
|
+
if (iterator == registry().end()) {
|
|
1567
|
+
if (!start.hasInteractiveStart ||
|
|
1568
|
+
!isFiniteProtocolV2Presentation(start.interactiveStart)) {
|
|
1569
|
+
return 0;
|
|
1570
|
+
}
|
|
1571
|
+
preflightStart = start.interactiveStart;
|
|
1572
|
+
} else {
|
|
1573
|
+
if (iterator->second.destroyed && !start.hasInteractiveStart) return 0;
|
|
1574
|
+
preflightStart = start.hasInteractiveStart
|
|
1575
|
+
? start.interactiveStart
|
|
1576
|
+
: visiblePresentation(driverId, iterator->second);
|
|
1577
|
+
}
|
|
1578
|
+
if (!isFiniteProtocolV2Presentation(preflightStart) ||
|
|
1579
|
+
preflightStart.clip.curve != presentation.clip.curve) {
|
|
1580
|
+
return 0;
|
|
1581
|
+
}
|
|
1582
|
+
const bool reduce = shouldReduceMotion(animation.reduceMotion) ||
|
|
1583
|
+
animation.durationMs <= 0;
|
|
1584
|
+
if (strictV2 && !reduce && iterator != registry().end() &&
|
|
1585
|
+
!timingPlanPreservesLinearHostNormalization(
|
|
1586
|
+
iterator->second, preflightStart, presentation, animation)) {
|
|
1587
|
+
return 0;
|
|
1588
|
+
}
|
|
740
1589
|
if (iterator == registry().end()) {
|
|
741
|
-
if (!start.hasInteractiveStart) return 0;
|
|
742
1590
|
iterator = registry().try_emplace(driverId).first;
|
|
743
1591
|
iterator->second.latest = start.interactiveStart;
|
|
744
1592
|
iterator->second.hasLatest = true;
|
|
745
1593
|
}
|
|
746
1594
|
auto &state = iterator->second;
|
|
1595
|
+
if (state.groupId != 0) finishGroupImpl(state.groupId, false, false);
|
|
747
1596
|
state.destroyed = false;
|
|
748
1597
|
const int32_t animationId = allocateAnimationId();
|
|
749
1598
|
const Presentation resolvedStart =
|
|
750
1599
|
prepareAnimation(driverId, state, start, presentation);
|
|
751
|
-
if (
|
|
1600
|
+
if (reduce) {
|
|
752
1601
|
applyPresentation(state, presentation);
|
|
753
1602
|
emitCompletion(driverId, animationId, true);
|
|
754
1603
|
return animationId;
|
|
@@ -767,16 +1616,53 @@ int32_t animateSpring(
|
|
|
767
1616
|
uint64_t driverId,
|
|
768
1617
|
AnimationStart start,
|
|
769
1618
|
Presentation presentation,
|
|
770
|
-
SpringAnimation animation
|
|
771
|
-
|
|
1619
|
+
SpringAnimation animation,
|
|
1620
|
+
AnimationValidationMode validationMode) {
|
|
1621
|
+
const bool strictV2 =
|
|
1622
|
+
validationMode == AnimationValidationMode::ProtocolV2;
|
|
1623
|
+
const bool validAnimation = strictV2
|
|
1624
|
+
? isValidProtocolV2Spring(animation)
|
|
1625
|
+
: std::isfinite(animation.mass) &&
|
|
1626
|
+
std::isfinite(animation.stiffness) &&
|
|
1627
|
+
std::isfinite(animation.damping) &&
|
|
1628
|
+
std::isfinite(animation.initialVelocity) &&
|
|
1629
|
+
isValidReduceMotionCode(animation.reduceMotion);
|
|
1630
|
+
if (!strictV2 && validAnimation) {
|
|
1631
|
+
animation.mass = std::max(0.0001, animation.mass);
|
|
1632
|
+
animation.stiffness = std::max(0.0001, animation.stiffness);
|
|
1633
|
+
animation.damping = std::max(0.0, animation.damping);
|
|
1634
|
+
}
|
|
1635
|
+
if (!isOnMainThread() || driverId == 0 ||
|
|
1636
|
+
!isFiniteProtocolV2Presentation(presentation) || !validAnimation) {
|
|
1637
|
+
return 0;
|
|
1638
|
+
}
|
|
772
1639
|
auto iterator = registry().find(driverId);
|
|
1640
|
+
Presentation preflightStart;
|
|
1641
|
+
if (iterator == registry().end()) {
|
|
1642
|
+
if (!start.hasInteractiveStart ||
|
|
1643
|
+
!isFiniteProtocolV2Presentation(start.interactiveStart)) {
|
|
1644
|
+
return 0;
|
|
1645
|
+
}
|
|
1646
|
+
preflightStart = start.interactiveStart;
|
|
1647
|
+
} else {
|
|
1648
|
+
if (iterator->second.destroyed && !start.hasInteractiveStart) return 0;
|
|
1649
|
+
preflightStart = start.hasInteractiveStart
|
|
1650
|
+
? start.interactiveStart
|
|
1651
|
+
: visiblePresentation(driverId, iterator->second);
|
|
1652
|
+
}
|
|
1653
|
+
if (!isFiniteProtocolV2Presentation(preflightStart) ||
|
|
1654
|
+
preflightStart.clip.curve != presentation.clip.curve ||
|
|
1655
|
+
(strictV2 && !protocolV2SpringScaleIsProvablyPositive(
|
|
1656
|
+
preflightStart, presentation, animation))) {
|
|
1657
|
+
return 0;
|
|
1658
|
+
}
|
|
773
1659
|
if (iterator == registry().end()) {
|
|
774
|
-
if (!start.hasInteractiveStart) return 0;
|
|
775
1660
|
iterator = registry().try_emplace(driverId).first;
|
|
776
1661
|
iterator->second.latest = start.interactiveStart;
|
|
777
1662
|
iterator->second.hasLatest = true;
|
|
778
1663
|
}
|
|
779
1664
|
auto &state = iterator->second;
|
|
1665
|
+
if (state.groupId != 0) finishGroupImpl(state.groupId, false, false);
|
|
780
1666
|
state.destroyed = false;
|
|
781
1667
|
const int32_t animationId = allocateAnimationId();
|
|
782
1668
|
// Scalar velocity along the current-to-target trajectory; each channel is
|
|
@@ -803,7 +1689,7 @@ int32_t animateSpring(
|
|
|
803
1689
|
active.durationS = kSpringMaxDurationS;
|
|
804
1690
|
const auto startChannels = toChannels(resolvedStart);
|
|
805
1691
|
const auto targetChannels = toChannels(presentation);
|
|
806
|
-
for (
|
|
1692
|
+
for (std::size_t index = 0; index < kChannelCount; index += 1) {
|
|
807
1693
|
active.springPosition[index] = startChannels[index];
|
|
808
1694
|
active.springVelocity[index] =
|
|
809
1695
|
velocity * (targetChannels[index] - startChannels[index]);
|
|
@@ -817,26 +1703,74 @@ int32_t animateKeyframes(
|
|
|
817
1703
|
Presentation presentation,
|
|
818
1704
|
double durationMs,
|
|
819
1705
|
std::vector<Keyframe> keyframes,
|
|
820
|
-
int32_t reduceMotion
|
|
821
|
-
|
|
1706
|
+
int32_t reduceMotion,
|
|
1707
|
+
AnimationValidationMode validationMode) {
|
|
1708
|
+
const bool strictV2 =
|
|
1709
|
+
validationMode == AnimationValidationMode::ProtocolV2;
|
|
1710
|
+
const bool finiteDuration = std::isfinite(durationMs);
|
|
1711
|
+
if (!strictV2 && finiteDuration) durationMs = std::max(0.0, durationMs);
|
|
1712
|
+
if (!isOnMainThread() || driverId == 0 ||
|
|
1713
|
+
!isFiniteProtocolV2Presentation(presentation) ||
|
|
1714
|
+
!finiteDuration || (strictV2 && durationMs < 0) ||
|
|
1715
|
+
!isValidReduceMotionCode(reduceMotion)) {
|
|
1716
|
+
return 0;
|
|
1717
|
+
}
|
|
822
1718
|
auto iterator = registry().find(driverId);
|
|
1719
|
+
Presentation preflightStart;
|
|
1720
|
+
if (iterator == registry().end()) {
|
|
1721
|
+
if (!start.hasInteractiveStart ||
|
|
1722
|
+
!isFiniteProtocolV2Presentation(start.interactiveStart)) {
|
|
1723
|
+
return 0;
|
|
1724
|
+
}
|
|
1725
|
+
preflightStart = start.interactiveStart;
|
|
1726
|
+
} else {
|
|
1727
|
+
if (iterator->second.destroyed && !start.hasInteractiveStart) return 0;
|
|
1728
|
+
preflightStart = start.hasInteractiveStart
|
|
1729
|
+
? start.interactiveStart
|
|
1730
|
+
: visiblePresentation(driverId, iterator->second);
|
|
1731
|
+
}
|
|
1732
|
+
bool validKeyframes = keyframes.size() >= 2 &&
|
|
1733
|
+
keyframes.front().offset == 0 && keyframes.back().offset == 1;
|
|
1734
|
+
double previousOffset = -1;
|
|
1735
|
+
for (const Keyframe &keyframe : keyframes) {
|
|
1736
|
+
validKeyframes = validKeyframes && std::isfinite(keyframe.offset) &&
|
|
1737
|
+
keyframe.offset > previousOffset && keyframe.offset >= 0 &&
|
|
1738
|
+
keyframe.offset <= 1 &&
|
|
1739
|
+
isFiniteProtocolV2Presentation(keyframe.presentation);
|
|
1740
|
+
previousOffset = keyframe.offset;
|
|
1741
|
+
}
|
|
1742
|
+
if (strictV2) {
|
|
1743
|
+
validKeyframes = isValidProtocolV2Keyframes(
|
|
1744
|
+
keyframes, preflightStart, presentation, start.hasInteractiveStart);
|
|
1745
|
+
}
|
|
1746
|
+
if (!isFiniteProtocolV2Presentation(preflightStart) || !validKeyframes) {
|
|
1747
|
+
return 0;
|
|
1748
|
+
}
|
|
1749
|
+
const bool reduce = shouldReduceMotion(reduceMotion) || durationMs <= 0;
|
|
1750
|
+
if (strictV2 && !reduce && iterator != registry().end() &&
|
|
1751
|
+
!keyframePlanPreservesLinearHostNormalization(
|
|
1752
|
+
iterator->second, preflightStart, keyframes)) {
|
|
1753
|
+
return 0;
|
|
1754
|
+
}
|
|
823
1755
|
if (iterator == registry().end()) {
|
|
824
|
-
if (!start.hasInteractiveStart) return 0;
|
|
825
1756
|
iterator = registry().try_emplace(driverId).first;
|
|
826
1757
|
iterator->second.latest = start.interactiveStart;
|
|
827
1758
|
iterator->second.hasLatest = true;
|
|
828
1759
|
}
|
|
829
1760
|
auto &state = iterator->second;
|
|
1761
|
+
if (state.groupId != 0) finishGroupImpl(state.groupId, false, false);
|
|
830
1762
|
state.destroyed = false;
|
|
831
1763
|
const int32_t animationId = allocateAnimationId();
|
|
832
1764
|
const Presentation resolvedStart =
|
|
833
1765
|
prepareAnimation(driverId, state, start, presentation);
|
|
834
|
-
if (
|
|
835
|
-
keyframes.size() < 2) {
|
|
1766
|
+
if (reduce) {
|
|
836
1767
|
applyPresentation(state, presentation);
|
|
837
1768
|
emitCompletion(driverId, animationId, true);
|
|
838
1769
|
return animationId;
|
|
839
1770
|
}
|
|
1771
|
+
if (strictV2 && !start.hasInteractiveStart) {
|
|
1772
|
+
keyframes.front().presentation = resolvedStart;
|
|
1773
|
+
}
|
|
840
1774
|
ActiveAnimation active;
|
|
841
1775
|
active.id = animationId;
|
|
842
1776
|
active.kind = AnimationKind::Keyframes;
|
|
@@ -866,6 +1800,18 @@ CancelResult cancelAnimation(
|
|
|
866
1800
|
return {false, {{0, 0, 0, 0, 0}, 0, 0}};
|
|
867
1801
|
}
|
|
868
1802
|
auto &state = iterator->second;
|
|
1803
|
+
if (state.groupId != 0) {
|
|
1804
|
+
const int32_t activeGroupId = state.groupId;
|
|
1805
|
+
if (animationId > 0 && animationId != activeGroupId) {
|
|
1806
|
+
return {false, visiblePresentation(driverId, state)};
|
|
1807
|
+
}
|
|
1808
|
+
const std::vector<DriverSnapshot> snapshots =
|
|
1809
|
+
finishGroupImpl(activeGroupId, useTarget, false);
|
|
1810
|
+
for (const DriverSnapshot &snapshot : snapshots) {
|
|
1811
|
+
if (snapshot.driverId == driverId) return {true, snapshot.presentation};
|
|
1812
|
+
}
|
|
1813
|
+
return {false, unavailablePresentation()};
|
|
1814
|
+
}
|
|
869
1815
|
if (!state.animation.has_value() ||
|
|
870
1816
|
(animationId > 0 && animationId != state.animation->id)) {
|
|
871
1817
|
return {false, state.animation.has_value() ? state.animation->current
|
|
@@ -888,6 +1834,7 @@ void destroyDriver(uint64_t driverId) {
|
|
|
888
1834
|
auto iterator = registry().find(driverId);
|
|
889
1835
|
if (iterator == registry().end()) return;
|
|
890
1836
|
auto &state = iterator->second;
|
|
1837
|
+
if (state.groupId != 0) finishGroupImpl(state.groupId, false, false);
|
|
891
1838
|
state.ownership = Ownership::Interactive;
|
|
892
1839
|
finishActive(driverId, state, false);
|
|
893
1840
|
// A destroyed driver's interaction history must not seed a revived
|
|
@@ -925,6 +1872,42 @@ void JSmoothClipView::applyClip(const Presentation &presentation) const {
|
|
|
925
1872
|
presentation.contentTranslateY);
|
|
926
1873
|
}
|
|
927
1874
|
|
|
1875
|
+
void JSmoothClipView::applyClipV2(const Presentation &presentation) const {
|
|
1876
|
+
static const auto method =
|
|
1877
|
+
javaClassStatic()
|
|
1878
|
+
->getMethod<void(
|
|
1879
|
+
jdouble,
|
|
1880
|
+
jdouble,
|
|
1881
|
+
jdouble,
|
|
1882
|
+
jdouble,
|
|
1883
|
+
jdouble,
|
|
1884
|
+
jdouble,
|
|
1885
|
+
jdouble,
|
|
1886
|
+
jdouble,
|
|
1887
|
+
jint,
|
|
1888
|
+
jdouble,
|
|
1889
|
+
jdouble,
|
|
1890
|
+
jdouble)>("setClipPresentationV2Dip");
|
|
1891
|
+
method(
|
|
1892
|
+
self(),
|
|
1893
|
+
presentation.clip.x,
|
|
1894
|
+
presentation.clip.y,
|
|
1895
|
+
presentation.clip.width,
|
|
1896
|
+
presentation.clip.height,
|
|
1897
|
+
SmoothClipResolvedRadius(
|
|
1898
|
+
presentation.clip.topLeftRadius, presentation.clip.radius),
|
|
1899
|
+
SmoothClipResolvedRadius(
|
|
1900
|
+
presentation.clip.topRightRadius, presentation.clip.radius),
|
|
1901
|
+
SmoothClipResolvedRadius(
|
|
1902
|
+
presentation.clip.bottomRightRadius, presentation.clip.radius),
|
|
1903
|
+
SmoothClipResolvedRadius(
|
|
1904
|
+
presentation.clip.bottomLeftRadius, presentation.clip.radius),
|
|
1905
|
+
static_cast<jint>(presentation.clip.curve),
|
|
1906
|
+
presentation.contentTranslateX,
|
|
1907
|
+
presentation.contentTranslateY,
|
|
1908
|
+
presentation.contentScale);
|
|
1909
|
+
}
|
|
1910
|
+
|
|
928
1911
|
void JSmoothClipView::applyClipPx(
|
|
929
1912
|
const NormalizedClip &clip,
|
|
930
1913
|
double contentTranslateXPx,
|
|
@@ -945,6 +1928,42 @@ void JSmoothClipView::applyClipPx(
|
|
|
945
1928
|
static_cast<jfloat>(contentTranslateYPx));
|
|
946
1929
|
}
|
|
947
1930
|
|
|
1931
|
+
void JSmoothClipView::applyClipV2Px(
|
|
1932
|
+
const NormalizedClip &clip,
|
|
1933
|
+
double contentTranslateXPx,
|
|
1934
|
+
double contentTranslateYPx,
|
|
1935
|
+
double contentScale) const {
|
|
1936
|
+
static const auto method =
|
|
1937
|
+
javaClassStatic()
|
|
1938
|
+
->getMethod<void(
|
|
1939
|
+
jfloat,
|
|
1940
|
+
jfloat,
|
|
1941
|
+
jfloat,
|
|
1942
|
+
jfloat,
|
|
1943
|
+
jfloat,
|
|
1944
|
+
jfloat,
|
|
1945
|
+
jfloat,
|
|
1946
|
+
jfloat,
|
|
1947
|
+
jint,
|
|
1948
|
+
jfloat,
|
|
1949
|
+
jfloat,
|
|
1950
|
+
jfloat)>("setClipPresentationV2Px");
|
|
1951
|
+
method(
|
|
1952
|
+
self(),
|
|
1953
|
+
static_cast<jfloat>(clip.left),
|
|
1954
|
+
static_cast<jfloat>(clip.top),
|
|
1955
|
+
static_cast<jfloat>(clip.right),
|
|
1956
|
+
static_cast<jfloat>(clip.bottom),
|
|
1957
|
+
static_cast<jfloat>(clip.topLeftRadius),
|
|
1958
|
+
static_cast<jfloat>(clip.topRightRadius),
|
|
1959
|
+
static_cast<jfloat>(clip.bottomRightRadius),
|
|
1960
|
+
static_cast<jfloat>(clip.bottomLeftRadius),
|
|
1961
|
+
static_cast<jint>(clip.curve),
|
|
1962
|
+
static_cast<jfloat>(contentTranslateXPx),
|
|
1963
|
+
static_cast<jfloat>(contentTranslateYPx),
|
|
1964
|
+
static_cast<jfloat>(contentScale));
|
|
1965
|
+
}
|
|
1966
|
+
|
|
948
1967
|
void registerViewAndroid(
|
|
949
1968
|
uint64_t driverId,
|
|
950
1969
|
alias_ref<JSmoothClipView> view,
|
|
@@ -959,9 +1978,7 @@ void registerViewAndroid(
|
|
|
959
1978
|
state.latest = initialPresentation;
|
|
960
1979
|
state.hasLatest = true;
|
|
961
1980
|
}
|
|
962
|
-
const Presentation visible = state
|
|
963
|
-
? state.animation->current
|
|
964
|
-
: state.latest;
|
|
1981
|
+
const Presentation visible = visiblePresentation(driverId, state);
|
|
965
1982
|
JNIEnv *env = facebook::jni::Environment::current();
|
|
966
1983
|
for (auto &existing : state.views) {
|
|
967
1984
|
if (env->IsSameObject(existing.view.get(), view.get())) {
|
|
@@ -971,6 +1988,7 @@ void registerViewAndroid(
|
|
|
971
1988
|
existing.lifecycleVisible = lifecycleVisible;
|
|
972
1989
|
deliverToView(existing, visible);
|
|
973
1990
|
reconcileViewDisplayability(driverId, state, existing);
|
|
1991
|
+
reconcileGroupReadiness(driverId);
|
|
974
1992
|
return;
|
|
975
1993
|
}
|
|
976
1994
|
}
|
|
@@ -986,6 +2004,7 @@ void registerViewAndroid(
|
|
|
986
2004
|
deliverToView(entry, visible);
|
|
987
2005
|
state.views.push_back(std::move(entry));
|
|
988
2006
|
reconcileViewDisplayability(driverId, state, state.views.back());
|
|
2007
|
+
reconcileGroupReadiness(driverId);
|
|
989
2008
|
}
|
|
990
2009
|
|
|
991
2010
|
void setViewHostGeometryAndroid(
|
|
@@ -1005,12 +2024,10 @@ void setViewHostGeometryAndroid(
|
|
|
1005
2024
|
entry.hostWidthPx = hostWidthPx;
|
|
1006
2025
|
entry.hostHeightPx = hostHeightPx;
|
|
1007
2026
|
if (state.hasLatest) {
|
|
1008
|
-
deliverToView(
|
|
1009
|
-
entry,
|
|
1010
|
-
state.animation.has_value() ? state.animation->current
|
|
1011
|
-
: state.latest);
|
|
2027
|
+
deliverToView(entry, visiblePresentation(driverId, state));
|
|
1012
2028
|
}
|
|
1013
2029
|
reconcileViewDisplayability(driverId, state, entry);
|
|
2030
|
+
reconcileGroupReadiness(driverId);
|
|
1014
2031
|
return;
|
|
1015
2032
|
}
|
|
1016
2033
|
}
|
|
@@ -1028,6 +2045,7 @@ void setViewLifecycleVisibilityAndroid(
|
|
|
1028
2045
|
if (!env->IsSameObject(entry.view.get(), view.get())) continue;
|
|
1029
2046
|
entry.lifecycleVisible = lifecycleVisible;
|
|
1030
2047
|
reconcileViewDisplayability(driverId, state, entry);
|
|
2048
|
+
reconcileGroupReadiness(driverId);
|
|
1031
2049
|
return;
|
|
1032
2050
|
}
|
|
1033
2051
|
}
|
|
@@ -1072,6 +2090,9 @@ void unregisterViewAndroid(
|
|
|
1072
2090
|
relatchIfNoDisplayable(driverId, state);
|
|
1073
2091
|
}
|
|
1074
2092
|
}
|
|
2093
|
+
if (removed && state.groupId != 0 && isOnMainThread()) {
|
|
2094
|
+
reconcileGroupReadiness(driverId);
|
|
2095
|
+
}
|
|
1075
2096
|
if (state.destroyed && state.views.empty()) {
|
|
1076
2097
|
registry().erase(iterator);
|
|
1077
2098
|
}
|