react-native-smooth-clip-view 0.2.9 → 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 +60 -21
- package/android/build.gradle +6 -2
- package/android/proguard-rules.pro +3 -3
- package/android/src/main/cpp/SmoothClipAndroid.h +10 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +890 -4
- package/android/src/main/cpp/SmoothClipRegistry.cpp +1056 -44
- 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 +371 -18
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +138 -24
- package/cpp/SmoothClipAnimationCurve.h +151 -122
- package/cpp/SmoothClipRegistry.h +95 -3
- package/cpp/SmoothClipSharedGeometry.h +146 -7
- package/cpp/SmoothClipVelocityTracker.h +13 -9
- package/ios/SmoothClipGeometry.h +184 -6
- package/ios/SmoothClipRegistry.mm +1028 -22
- 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 +263 -63
- 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 +25 -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 +42 -11
- package/src/drivers.native.ts +824 -124
- 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
|
@@ -57,14 +57,14 @@ struct ActiveAnimation {
|
|
|
57
57
|
Presentation current{{0, 0, 0, 0, 0}, 0, 0};
|
|
58
58
|
TimingAnimation timing{};
|
|
59
59
|
SpringAnimation spring{};
|
|
60
|
-
// Owns the keyframes and
|
|
61
|
-
// tangent solve happens on reset(), never per frame.
|
|
60
|
+
// Owns the segment-linear keyframes and a cursor into the active segment.
|
|
62
61
|
KeyframeCurve keyframes;
|
|
63
62
|
double durationS = 0;
|
|
64
63
|
double startedAtS = 0;
|
|
65
|
-
// Integrated spring state (
|
|
66
|
-
|
|
67
|
-
|
|
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{};
|
|
68
68
|
double lastFrameS = 0;
|
|
69
69
|
bool finished = true;
|
|
70
70
|
// False while the animation is latched, and held out of animatingDrivers()
|
|
@@ -104,6 +104,8 @@ struct DriverState {
|
|
|
104
104
|
// replay, hosts mounted in another subtree). The entry is erased when the
|
|
105
105
|
// last view leaves and revived by a take-ownership setPresentation.
|
|
106
106
|
bool destroyed = false;
|
|
107
|
+
// Non-zero while this driver is an immutable member of one active group.
|
|
108
|
+
int32_t groupId = 0;
|
|
107
109
|
|
|
108
110
|
// 'inherit' velocity samples; recording/coalescing/projection live in the
|
|
109
111
|
// shared cpp/SmoothClipVelocityTracker.h (behavior-paired with iOS).
|
|
@@ -115,6 +117,31 @@ struct CompletionSink {
|
|
|
115
117
|
CompletionCallback callback;
|
|
116
118
|
};
|
|
117
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
|
+
|
|
118
145
|
std::unordered_map<uint64_t, DriverState> ®istry() {
|
|
119
146
|
static std::unordered_map<uint64_t, DriverState> value;
|
|
120
147
|
return value;
|
|
@@ -125,6 +152,21 @@ CompletionSink &completionSink() {
|
|
|
125
152
|
return sink;
|
|
126
153
|
}
|
|
127
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
|
+
|
|
128
170
|
// The sink is written from the JS thread (installBindings / invalidate) and
|
|
129
171
|
// invoked on main. Invoking under the lock also keeps the owning bindings
|
|
130
172
|
// state alive for the duration of the callback.
|
|
@@ -242,7 +284,7 @@ bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
|
242
284
|
while (dt > 0) {
|
|
243
285
|
const double step = std::min(dt, kMaxStepS);
|
|
244
286
|
dt -= step;
|
|
245
|
-
for (
|
|
287
|
+
for (std::size_t index = 0; index < kChannelCount; index += 1) {
|
|
246
288
|
const double displacement =
|
|
247
289
|
animation.springPosition[index] - target[index];
|
|
248
290
|
const double acceleration =
|
|
@@ -254,7 +296,7 @@ bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
|
254
296
|
}
|
|
255
297
|
}
|
|
256
298
|
bool settled = true;
|
|
257
|
-
for (
|
|
299
|
+
for (std::size_t index = 0; index < kChannelCount; index += 1) {
|
|
258
300
|
if (!std::isfinite(animation.springPosition[index]) ||
|
|
259
301
|
!std::isfinite(animation.springVelocity[index])) {
|
|
260
302
|
// A divergent configuration must never freeze the clip: end at target.
|
|
@@ -269,7 +311,8 @@ bool advanceSpring(ActiveAnimation &animation, double now) {
|
|
|
269
311
|
settled = false;
|
|
270
312
|
}
|
|
271
313
|
}
|
|
272
|
-
animation.current =
|
|
314
|
+
animation.current =
|
|
315
|
+
fromChannels(animation.springPosition, animation.start.clip.curve);
|
|
273
316
|
return settled || now - animation.startedAtS >= kSpringMaxDurationS;
|
|
274
317
|
}
|
|
275
318
|
|
|
@@ -296,32 +339,117 @@ void emitCompletion(uint64_t driverId, int32_t animationId, bool finished) {
|
|
|
296
339
|
}
|
|
297
340
|
}
|
|
298
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
|
+
|
|
299
394
|
// Terminal per-frame delivery: scale DIP -> px and normalize against the
|
|
300
395
|
// pushed host metrics in C++, so the JNI call carries final pixel floats and
|
|
301
396
|
// the Kotlin side reduces to field stores + invalidateOutline().
|
|
302
397
|
void deliverToView(const ViewEntry &entry, const Presentation &presentation) {
|
|
398
|
+
const bool deliverV2 = needsV2Delivery(presentation);
|
|
303
399
|
if (entry.density <= 0) {
|
|
304
|
-
|
|
400
|
+
if (deliverV2) {
|
|
401
|
+
entry.view->applyClipV2(presentation);
|
|
402
|
+
} else {
|
|
403
|
+
entry.view->applyClip(presentation);
|
|
404
|
+
}
|
|
305
405
|
return;
|
|
306
406
|
}
|
|
307
407
|
const double density = entry.density;
|
|
308
408
|
NormalizedClip clip;
|
|
309
|
-
if (
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
+
}
|
|
319
438
|
}
|
|
320
439
|
const double translateX = presentation.contentTranslateX * density;
|
|
321
440
|
const double translateY = presentation.contentTranslateY * density;
|
|
322
441
|
// Atomic reject, mirroring the Kotlin DIP path's all-or-nothing gate.
|
|
323
|
-
if (!std::isfinite(translateX) || !std::isfinite(translateY)
|
|
324
|
-
|
|
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
|
+
}
|
|
325
453
|
}
|
|
326
454
|
|
|
327
455
|
void applyToViews(DriverState &state, const Presentation &presentation) {
|
|
@@ -369,8 +497,10 @@ Presentation prepareAnimation(
|
|
|
369
497
|
Presentation target) {
|
|
370
498
|
const Presentation visibleBefore =
|
|
371
499
|
state.animation.has_value() ? state.animation->current : state.latest;
|
|
372
|
-
|
|
373
|
-
|
|
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;
|
|
374
504
|
finishActive(driverId, state, false);
|
|
375
505
|
const Presentation resolvedStart =
|
|
376
506
|
acceptsInteractiveStart ? start.interactiveStart : visibleBefore;
|
|
@@ -392,6 +522,77 @@ bool entryDisplayable(const ViewEntry &entry) {
|
|
|
392
522
|
entry.lifecycleVisible;
|
|
393
523
|
}
|
|
394
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
|
+
|
|
395
596
|
bool anyDisplayableView(const DriverState &state) {
|
|
396
597
|
for (const ViewEntry &entry : state.views) {
|
|
397
598
|
if (entryDisplayable(entry)) return true;
|
|
@@ -399,6 +600,141 @@ bool anyDisplayableView(const DriverState &state) {
|
|
|
399
600
|
return false;
|
|
400
601
|
}
|
|
401
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
|
+
|
|
402
738
|
// Starts a latched animation: rebases the clock so no progress was burned
|
|
403
739
|
// while no view could display, then joins the choreographer loop.
|
|
404
740
|
void startLatchedAnimation(uint64_t driverId, DriverState &state) {
|
|
@@ -615,6 +951,54 @@ void advance(uint64_t driverId, DriverState &state, double now) {
|
|
|
615
951
|
}
|
|
616
952
|
}
|
|
617
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
|
+
|
|
618
1002
|
void onFrameImpl(double now) {
|
|
619
1003
|
SMOOTH_CLIP_TRACE("SmoothClip.frame");
|
|
620
1004
|
gFrameScheduled = false;
|
|
@@ -634,7 +1018,14 @@ void onFrameImpl(double now) {
|
|
|
634
1018
|
}
|
|
635
1019
|
advance(driverId, iterator->second, now);
|
|
636
1020
|
}
|
|
637
|
-
|
|
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();
|
|
638
1029
|
}
|
|
639
1030
|
|
|
640
1031
|
} // namespace
|
|
@@ -673,6 +1064,407 @@ void clearCompletionCallback(const void *owner) {
|
|
|
673
1064
|
completionSink().callback = nullptr;
|
|
674
1065
|
}
|
|
675
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
|
+
|
|
676
1468
|
void setPresentation(
|
|
677
1469
|
uint64_t driverId,
|
|
678
1470
|
Presentation presentation,
|
|
@@ -692,6 +1484,10 @@ void setPresentation(
|
|
|
692
1484
|
iterator = registry().try_emplace(driverId).first;
|
|
693
1485
|
}
|
|
694
1486
|
auto &state = iterator->second;
|
|
1487
|
+
if (state.groupId != 0) {
|
|
1488
|
+
if (!takeOwnership) return;
|
|
1489
|
+
finishGroupImpl(state.groupId, false, false);
|
|
1490
|
+
}
|
|
695
1491
|
if (!takeOwnership && state.ownership != Ownership::Interactive) return;
|
|
696
1492
|
if (takeOwnership) {
|
|
697
1493
|
state.destroyed = false;
|
|
@@ -721,6 +1517,9 @@ Presentation beginInteraction(uint64_t driverId) {
|
|
|
721
1517
|
auto iterator = registry().find(driverId);
|
|
722
1518
|
if (iterator == registry().end()) return unavailablePresentation();
|
|
723
1519
|
auto &state = iterator->second;
|
|
1520
|
+
if (state.groupId != 0) {
|
|
1521
|
+
finishGroupImpl(state.groupId, false, false);
|
|
1522
|
+
}
|
|
724
1523
|
state.destroyed = false;
|
|
725
1524
|
if (!state.animation.has_value()) {
|
|
726
1525
|
state.ownership = Ownership::Interactive;
|
|
@@ -743,21 +1542,62 @@ int32_t animateTiming(
|
|
|
743
1542
|
uint64_t driverId,
|
|
744
1543
|
AnimationStart start,
|
|
745
1544
|
Presentation presentation,
|
|
746
|
-
TimingAnimation animation
|
|
747
|
-
|
|
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
|
+
}
|
|
748
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
|
+
}
|
|
749
1589
|
if (iterator == registry().end()) {
|
|
750
|
-
if (!start.hasInteractiveStart) return 0;
|
|
751
1590
|
iterator = registry().try_emplace(driverId).first;
|
|
752
1591
|
iterator->second.latest = start.interactiveStart;
|
|
753
1592
|
iterator->second.hasLatest = true;
|
|
754
1593
|
}
|
|
755
1594
|
auto &state = iterator->second;
|
|
1595
|
+
if (state.groupId != 0) finishGroupImpl(state.groupId, false, false);
|
|
756
1596
|
state.destroyed = false;
|
|
757
1597
|
const int32_t animationId = allocateAnimationId();
|
|
758
1598
|
const Presentation resolvedStart =
|
|
759
1599
|
prepareAnimation(driverId, state, start, presentation);
|
|
760
|
-
if (
|
|
1600
|
+
if (reduce) {
|
|
761
1601
|
applyPresentation(state, presentation);
|
|
762
1602
|
emitCompletion(driverId, animationId, true);
|
|
763
1603
|
return animationId;
|
|
@@ -776,16 +1616,53 @@ int32_t animateSpring(
|
|
|
776
1616
|
uint64_t driverId,
|
|
777
1617
|
AnimationStart start,
|
|
778
1618
|
Presentation presentation,
|
|
779
|
-
SpringAnimation animation
|
|
780
|
-
|
|
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
|
+
}
|
|
781
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
|
+
}
|
|
782
1659
|
if (iterator == registry().end()) {
|
|
783
|
-
if (!start.hasInteractiveStart) return 0;
|
|
784
1660
|
iterator = registry().try_emplace(driverId).first;
|
|
785
1661
|
iterator->second.latest = start.interactiveStart;
|
|
786
1662
|
iterator->second.hasLatest = true;
|
|
787
1663
|
}
|
|
788
1664
|
auto &state = iterator->second;
|
|
1665
|
+
if (state.groupId != 0) finishGroupImpl(state.groupId, false, false);
|
|
789
1666
|
state.destroyed = false;
|
|
790
1667
|
const int32_t animationId = allocateAnimationId();
|
|
791
1668
|
// Scalar velocity along the current-to-target trajectory; each channel is
|
|
@@ -812,7 +1689,7 @@ int32_t animateSpring(
|
|
|
812
1689
|
active.durationS = kSpringMaxDurationS;
|
|
813
1690
|
const auto startChannels = toChannels(resolvedStart);
|
|
814
1691
|
const auto targetChannels = toChannels(presentation);
|
|
815
|
-
for (
|
|
1692
|
+
for (std::size_t index = 0; index < kChannelCount; index += 1) {
|
|
816
1693
|
active.springPosition[index] = startChannels[index];
|
|
817
1694
|
active.springVelocity[index] =
|
|
818
1695
|
velocity * (targetChannels[index] - startChannels[index]);
|
|
@@ -826,26 +1703,74 @@ int32_t animateKeyframes(
|
|
|
826
1703
|
Presentation presentation,
|
|
827
1704
|
double durationMs,
|
|
828
1705
|
std::vector<Keyframe> keyframes,
|
|
829
|
-
int32_t reduceMotion
|
|
830
|
-
|
|
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
|
+
}
|
|
831
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
|
+
}
|
|
832
1755
|
if (iterator == registry().end()) {
|
|
833
|
-
if (!start.hasInteractiveStart) return 0;
|
|
834
1756
|
iterator = registry().try_emplace(driverId).first;
|
|
835
1757
|
iterator->second.latest = start.interactiveStart;
|
|
836
1758
|
iterator->second.hasLatest = true;
|
|
837
1759
|
}
|
|
838
1760
|
auto &state = iterator->second;
|
|
1761
|
+
if (state.groupId != 0) finishGroupImpl(state.groupId, false, false);
|
|
839
1762
|
state.destroyed = false;
|
|
840
1763
|
const int32_t animationId = allocateAnimationId();
|
|
841
1764
|
const Presentation resolvedStart =
|
|
842
1765
|
prepareAnimation(driverId, state, start, presentation);
|
|
843
|
-
if (
|
|
844
|
-
keyframes.size() < 2) {
|
|
1766
|
+
if (reduce) {
|
|
845
1767
|
applyPresentation(state, presentation);
|
|
846
1768
|
emitCompletion(driverId, animationId, true);
|
|
847
1769
|
return animationId;
|
|
848
1770
|
}
|
|
1771
|
+
if (strictV2 && !start.hasInteractiveStart) {
|
|
1772
|
+
keyframes.front().presentation = resolvedStart;
|
|
1773
|
+
}
|
|
849
1774
|
ActiveAnimation active;
|
|
850
1775
|
active.id = animationId;
|
|
851
1776
|
active.kind = AnimationKind::Keyframes;
|
|
@@ -875,6 +1800,18 @@ CancelResult cancelAnimation(
|
|
|
875
1800
|
return {false, {{0, 0, 0, 0, 0}, 0, 0}};
|
|
876
1801
|
}
|
|
877
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
|
+
}
|
|
878
1815
|
if (!state.animation.has_value() ||
|
|
879
1816
|
(animationId > 0 && animationId != state.animation->id)) {
|
|
880
1817
|
return {false, state.animation.has_value() ? state.animation->current
|
|
@@ -897,6 +1834,7 @@ void destroyDriver(uint64_t driverId) {
|
|
|
897
1834
|
auto iterator = registry().find(driverId);
|
|
898
1835
|
if (iterator == registry().end()) return;
|
|
899
1836
|
auto &state = iterator->second;
|
|
1837
|
+
if (state.groupId != 0) finishGroupImpl(state.groupId, false, false);
|
|
900
1838
|
state.ownership = Ownership::Interactive;
|
|
901
1839
|
finishActive(driverId, state, false);
|
|
902
1840
|
// A destroyed driver's interaction history must not seed a revived
|
|
@@ -934,6 +1872,42 @@ void JSmoothClipView::applyClip(const Presentation &presentation) const {
|
|
|
934
1872
|
presentation.contentTranslateY);
|
|
935
1873
|
}
|
|
936
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
|
+
|
|
937
1911
|
void JSmoothClipView::applyClipPx(
|
|
938
1912
|
const NormalizedClip &clip,
|
|
939
1913
|
double contentTranslateXPx,
|
|
@@ -954,6 +1928,42 @@ void JSmoothClipView::applyClipPx(
|
|
|
954
1928
|
static_cast<jfloat>(contentTranslateYPx));
|
|
955
1929
|
}
|
|
956
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
|
+
|
|
957
1967
|
void registerViewAndroid(
|
|
958
1968
|
uint64_t driverId,
|
|
959
1969
|
alias_ref<JSmoothClipView> view,
|
|
@@ -968,9 +1978,7 @@ void registerViewAndroid(
|
|
|
968
1978
|
state.latest = initialPresentation;
|
|
969
1979
|
state.hasLatest = true;
|
|
970
1980
|
}
|
|
971
|
-
const Presentation visible = state
|
|
972
|
-
? state.animation->current
|
|
973
|
-
: state.latest;
|
|
1981
|
+
const Presentation visible = visiblePresentation(driverId, state);
|
|
974
1982
|
JNIEnv *env = facebook::jni::Environment::current();
|
|
975
1983
|
for (auto &existing : state.views) {
|
|
976
1984
|
if (env->IsSameObject(existing.view.get(), view.get())) {
|
|
@@ -980,6 +1988,7 @@ void registerViewAndroid(
|
|
|
980
1988
|
existing.lifecycleVisible = lifecycleVisible;
|
|
981
1989
|
deliverToView(existing, visible);
|
|
982
1990
|
reconcileViewDisplayability(driverId, state, existing);
|
|
1991
|
+
reconcileGroupReadiness(driverId);
|
|
983
1992
|
return;
|
|
984
1993
|
}
|
|
985
1994
|
}
|
|
@@ -995,6 +2004,7 @@ void registerViewAndroid(
|
|
|
995
2004
|
deliverToView(entry, visible);
|
|
996
2005
|
state.views.push_back(std::move(entry));
|
|
997
2006
|
reconcileViewDisplayability(driverId, state, state.views.back());
|
|
2007
|
+
reconcileGroupReadiness(driverId);
|
|
998
2008
|
}
|
|
999
2009
|
|
|
1000
2010
|
void setViewHostGeometryAndroid(
|
|
@@ -1014,12 +2024,10 @@ void setViewHostGeometryAndroid(
|
|
|
1014
2024
|
entry.hostWidthPx = hostWidthPx;
|
|
1015
2025
|
entry.hostHeightPx = hostHeightPx;
|
|
1016
2026
|
if (state.hasLatest) {
|
|
1017
|
-
deliverToView(
|
|
1018
|
-
entry,
|
|
1019
|
-
state.animation.has_value() ? state.animation->current
|
|
1020
|
-
: state.latest);
|
|
2027
|
+
deliverToView(entry, visiblePresentation(driverId, state));
|
|
1021
2028
|
}
|
|
1022
2029
|
reconcileViewDisplayability(driverId, state, entry);
|
|
2030
|
+
reconcileGroupReadiness(driverId);
|
|
1023
2031
|
return;
|
|
1024
2032
|
}
|
|
1025
2033
|
}
|
|
@@ -1037,6 +2045,7 @@ void setViewLifecycleVisibilityAndroid(
|
|
|
1037
2045
|
if (!env->IsSameObject(entry.view.get(), view.get())) continue;
|
|
1038
2046
|
entry.lifecycleVisible = lifecycleVisible;
|
|
1039
2047
|
reconcileViewDisplayability(driverId, state, entry);
|
|
2048
|
+
reconcileGroupReadiness(driverId);
|
|
1040
2049
|
return;
|
|
1041
2050
|
}
|
|
1042
2051
|
}
|
|
@@ -1081,6 +2090,9 @@ void unregisterViewAndroid(
|
|
|
1081
2090
|
relatchIfNoDisplayable(driverId, state);
|
|
1082
2091
|
}
|
|
1083
2092
|
}
|
|
2093
|
+
if (removed && state.groupId != 0 && isOnMainThread()) {
|
|
2094
|
+
reconcileGroupReadiness(driverId);
|
|
2095
|
+
}
|
|
1084
2096
|
if (state.destroyed && state.views.empty()) {
|
|
1085
2097
|
registry().erase(iterator);
|
|
1086
2098
|
}
|