1 /*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 #include "ABI49_0_0LayoutAnimationKeyFrameManager.h"
9
10 #include <algorithm>
11 #include <sstream>
12 #include <utility>
13
14 #include <ABI49_0_0React/debug/ABI49_0_0flags.h>
15 #include <ABI49_0_0React/debug/ABI49_0_0React_native_assert.h>
16
17 #include <ABI49_0_0React/renderer/animations/ABI49_0_0conversions.h>
18 #include <ABI49_0_0React/renderer/animations/ABI49_0_0utils.h>
19 #include <ABI49_0_0React/renderer/componentregistry/ABI49_0_0ComponentDescriptorFactory.h>
20 #include <ABI49_0_0React/ABI49_0_0renderer/components/image/ImageProps.h>
21 #include <ABI49_0_0React/ABI49_0_0renderer/components/view/ViewProps.h>
22 #include <ABI49_0_0React/renderer/core/ABI49_0_0ComponentDescriptor.h>
23 #include <ABI49_0_0React/renderer/core/ABI49_0_0LayoutMetrics.h>
24 #include <ABI49_0_0React/renderer/core/ABI49_0_0Props.h>
25 #include <ABI49_0_0React/renderer/core/ABI49_0_0PropsParserContext.h>
26 #include <ABI49_0_0React/renderer/core/ABI49_0_0RawValue.h>
27 #include <ABI49_0_0React/renderer/mounting/ABI49_0_0MountingCoordinator.h>
28 #include <ABI49_0_0React/renderer/mounting/ABI49_0_0ShadowView.h>
29 #include <ABI49_0_0React/renderer/mounting/ABI49_0_0ShadowViewMutation.h>
30
31 #include <glog/logging.h>
32
33 namespace ABI49_0_0facebook::ABI49_0_0React {
34
35 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
GetMutationInstructionString(ShadowViewMutation const & mutation)36 static std::string GetMutationInstructionString(
37 ShadowViewMutation const &mutation) {
38 Tag tag = mutation.type == ShadowViewMutation::Type::Insert ||
39 mutation.type == ShadowViewMutation::Type::Create
40 ? mutation.newChildShadowView.tag
41 : mutation.oldChildShadowView.tag;
42 return getDebugName(mutation) + " [" + std::to_string(tag) + "]->[" +
43 std::to_string(mutation.parentShadowView.tag) + "] @" +
44 std::to_string(mutation.index);
45 }
46
PrintMutationInstruction(std::string message,ShadowViewMutation const & mutation)47 void PrintMutationInstruction(
48 std::string message,
49 ShadowViewMutation const &mutation) {
50 [&](std::ostream &stream) -> std::ostream & {
51 stream << message
52 << " Mutation: " << GetMutationInstructionString(mutation);
53 if (mutation.oldChildShadowView.tag != 0) {
54 stream << " old hash: ##"
55 << std::hash<ShadowView>{}(mutation.oldChildShadowView);
56 }
57 if (mutation.newChildShadowView.tag != 0) {
58 stream << " new hash: ##"
59 << std::hash<ShadowView>{}(mutation.newChildShadowView);
60 }
61 return stream;
62 }(LOG(ERROR));
63 }
64
PrintMutationInstructionRelative(std::string message,ShadowViewMutation const & mutation,ShadowViewMutation const & relativeMutation)65 void PrintMutationInstructionRelative(
66 std::string message,
67 ShadowViewMutation const &mutation,
68 ShadowViewMutation const &relativeMutation) {
69 LOG(ERROR) << message
70 << " Mutation: " << GetMutationInstructionString(mutation)
71 << " RelativeMutation: "
72 << GetMutationInstructionString(relativeMutation);
73 }
74 #endif
75
76 static inline Float
interpolateFloats(Float coefficient,Float oldValue,Float newValue)77 interpolateFloats(Float coefficient, Float oldValue, Float newValue) {
78 return oldValue + (newValue - oldValue) * coefficient;
79 }
80
81 #pragma mark -
82
LayoutAnimationKeyFrameManager(RuntimeExecutor runtimeExecutor,ContextContainer::Shared & contextContainer,LayoutAnimationStatusDelegate * delegate)83 LayoutAnimationKeyFrameManager::LayoutAnimationKeyFrameManager(
84 RuntimeExecutor runtimeExecutor,
85 ContextContainer::Shared &contextContainer,
86 LayoutAnimationStatusDelegate *delegate)
87 : runtimeExecutor_(std::move(runtimeExecutor)),
88 contextContainer_(contextContainer),
89 layoutAnimationStatusDelegate_(delegate),
90 now_([]() {
91 return std::chrono::duration_cast<std::chrono::milliseconds>(
92 std::chrono::high_resolution_clock::now().time_since_epoch())
93 .count();
94 }) {}
95
96 #pragma mark UIManagerAnimationDelegate methods
97
98 /**
99 * Globally configure next LayoutAnimation.
100 * This is guaranteed to be called only on the JS thread.
101 */
uiManagerDidConfigureNextLayoutAnimation(jsi::Runtime & runtime,RawValue const & config,const jsi::Value & successCallbackValue,const jsi::Value & failureCallbackValue) const102 void LayoutAnimationKeyFrameManager::uiManagerDidConfigureNextLayoutAnimation(
103 jsi::Runtime &runtime,
104 RawValue const &config,
105 const jsi::Value &successCallbackValue,
106 const jsi::Value &failureCallbackValue) const {
107 bool hasSuccessCallback = successCallbackValue.isObject() &&
108 successCallbackValue.getObject(runtime).isFunction(runtime);
109 bool hasFailureCallback = failureCallbackValue.isObject() &&
110 failureCallbackValue.getObject(runtime).isFunction(runtime);
111 LayoutAnimationCallbackWrapper successCallback = hasSuccessCallback
112 ? LayoutAnimationCallbackWrapper(
113 successCallbackValue.getObject(runtime).getFunction(runtime))
114 : LayoutAnimationCallbackWrapper();
115 LayoutAnimationCallbackWrapper failureCallback = hasFailureCallback
116 ? LayoutAnimationCallbackWrapper(
117 failureCallbackValue.getObject(runtime).getFunction(runtime))
118 : LayoutAnimationCallbackWrapper();
119
120 auto layoutAnimationConfig =
121 parseLayoutAnimationConfig((folly::dynamic)config);
122
123 if (layoutAnimationConfig) {
124 std::lock_guard<std::mutex> lock(currentAnimationMutex_);
125
126 uiManagerDidConfigureNextLayoutAnimation(LayoutAnimation{
127 -1,
128 0,
129 false,
130 *layoutAnimationConfig,
131 successCallback,
132 failureCallback,
133 {}});
134 } else {
135 LOG(ERROR) << "Parsing LayoutAnimationConfig failed: "
136 << (folly::dynamic)config;
137
138 callCallback(failureCallback);
139 }
140 }
141
setComponentDescriptorRegistry(const SharedComponentDescriptorRegistry & componentDescriptorRegistry)142 void LayoutAnimationKeyFrameManager::setComponentDescriptorRegistry(
143 const SharedComponentDescriptorRegistry &componentDescriptorRegistry) {
144 componentDescriptorRegistry_ = componentDescriptorRegistry;
145 }
146
setReduceDeleteCreateMutation(const bool reduceDeleteCreateMutation)147 void LayoutAnimationKeyFrameManager::setReduceDeleteCreateMutation(
148 const bool reduceDeleteCreateMutation) {
149 reduceDeleteCreateMutation_ = reduceDeleteCreateMutation;
150 }
151
shouldAnimateFrame() const152 bool LayoutAnimationKeyFrameManager::shouldAnimateFrame() const {
153 std::lock_guard<std::mutex> lock(currentAnimationMutex_);
154 return currentAnimation_ || !inflightAnimations_.empty();
155 }
156
stopSurface(SurfaceId surfaceId)157 void LayoutAnimationKeyFrameManager::stopSurface(SurfaceId surfaceId) {
158 std::lock_guard<std::mutex> lock(surfaceIdsToStopMutex_);
159 surfaceIdsToStop_.insert(surfaceId);
160 }
161
162 #pragma mark - MountingOverrideDelegate methods
163
shouldOverridePullTransaction() const164 bool LayoutAnimationKeyFrameManager::shouldOverridePullTransaction() const {
165 return shouldAnimateFrame();
166 }
167
168 std::optional<MountingTransaction>
pullTransaction(SurfaceId surfaceId,MountingTransaction::Number transactionNumber,TransactionTelemetry const & telemetry,ShadowViewMutationList mutations) const169 LayoutAnimationKeyFrameManager::pullTransaction(
170 SurfaceId surfaceId,
171 MountingTransaction::Number transactionNumber,
172 TransactionTelemetry const &telemetry,
173 ShadowViewMutationList mutations) const {
174 // Current time in milliseconds
175 uint64_t now = now_();
176
177 bool inflightAnimationsExistInitially = !inflightAnimations_.empty();
178 deleteAnimationsForStoppedSurfaces();
179
180 if (!mutations.empty()) {
181 #ifdef ABI49_0_0RN_SHADOW_TREE_INTROSPECTION
182 {
183 std::stringstream ss(getDebugDescription(mutations, {}));
184 std::string to;
185 while (std::getline(ss, to, '\n')) {
186 LOG(ERROR)
187 << "LayoutAnimationKeyFrameManager.cpp: got mutation list: Line: "
188 << to;
189 }
190 };
191 #endif
192
193 // DEBUG ONLY: list existing inflight animations
194 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
195 LOG(ERROR) << "BEGINNING DISPLAYING ONGOING inflightAnimations_!";
196 int i = 0;
197 int j = 0;
198 for (auto const &inflightAnimation : inflightAnimations_) {
199 i++;
200 j = 0;
201 if (inflightAnimation.completed) {
202 continue;
203 }
204 for (auto &keyframe : inflightAnimation.keyFrames) {
205 j++;
206 if (keyframe.invalidated) {
207 continue;
208 }
209 for (const auto &finalMutationForKeyFrame :
210 keyframe.finalMutationsForKeyFrame) {
211 if (finalMutationForKeyFrame.mutatedViewIsVirtual()) {
212 std::string msg = "Animation " + std::to_string(i) + " keyframe " +
213 std::to_string(j) + ": Final Animation";
214 PrintMutationInstruction(msg, finalMutationForKeyFrame);
215 } else {
216 LOG(ERROR) << "Animation " << i << " keyframe " << j
217 << ": on tag: [" << keyframe.viewStart.tag << "]";
218 }
219 }
220 }
221 }
222 LOG(ERROR) << "BEGINNING DONE DISPLAYING ONGOING inflightAnimations_!";
223 #endif
224
225 PropsParserContext propsParserContext{surfaceId, *contextContainer_};
226
227 // What to do if we detect a conflict? Get current value and make
228 // that the baseline of the next animation. Scale the remaining time
229 // in the animation
230 // Types of conflicts and how we handle them:
231 // Update -> update: remove the previous update, make it the baseline of the
232 // next update (with current progress) Update -> remove: same, with final
233 // mutation being a remove Insert -> update: treat as update->update Insert
234 // -> remove: same, as update->remove Remove -> update/insert: not possible
235 // We just collect pairs here of <Mutation, AnimationConfig> and delete them
236 // from active animations. If another animation is queued up from the
237 // current mutations then these deleted mutations will serve as the baseline
238 // for the next animation. If not, the current mutations are executed
239 // immediately without issues.
240 std::vector<AnimationKeyFrame> conflictingAnimations{};
241 getAndEraseConflictingAnimations(
242 surfaceId, mutations, conflictingAnimations);
243
244 // Are we animating this list of mutations?
245 std::optional<LayoutAnimation> currentAnimation{};
246 {
247 std::lock_guard<std::mutex> lock(currentAnimationMutex_);
248 if (currentAnimation_) {
249 currentAnimation = std::move(currentAnimation_);
250 currentAnimation_.reset();
251 }
252 }
253
254 if (currentAnimation.has_value()) {
255 LayoutAnimation animation = std::move(currentAnimation).value();
256 animation.surfaceId = surfaceId;
257 animation.startTime = now;
258
259 // Pre-process list to:
260 // Catch remove+reinsert (reorders)
261 // Catch delete+create (reparenting) (this should be optimized away at
262 // the diffing level eventually?)
263 // TODO: to prevent this step we could tag Remove/Insert mutations as
264 // being moves on the Differ level, since we know that there? We could use
265 // TinyMap here, but it's not exposed by Differentiator (yet).
266 butter::set<Tag> insertedTags;
267 butter::set<Tag> deletedTags;
268 butter::set<Tag> reparentedTags; // tags that are deleted and recreated
269 std::unordered_map<Tag, ShadowViewMutation> movedTags;
270 for (const auto &mutation : mutations) {
271 if (mutation.type == ShadowViewMutation::Type::Insert) {
272 insertedTags.insert(mutation.newChildShadowView.tag);
273 }
274 if (mutation.type == ShadowViewMutation::Type::Delete) {
275 deletedTags.insert(mutation.oldChildShadowView.tag);
276 }
277 if (mutation.type == ShadowViewMutation::Type::Create) {
278 if (deletedTags.find(mutation.newChildShadowView.tag) !=
279 deletedTags.end()) {
280 reparentedTags.insert(mutation.newChildShadowView.tag);
281 }
282 }
283 }
284
285 // Process mutations list into operations that can be sent to platform
286 // immediately, and those that need to be animated Deletions, removals,
287 // updates are delayed and animated. Creations and insertions are sent to
288 // platform and then "animated in" with opacity updates. Upon completion,
289 // removals and deletions are sent to platform
290 ShadowViewMutation::List immediateMutations;
291
292 // Remove operations that are actually moves should be copied to
293 // "immediate mutations". The corresponding "insert" will also be executed
294 // immediately and animated as an update.
295 std::vector<AnimationKeyFrame> keyFramesToAnimate;
296 auto const layoutAnimationConfig = animation.layoutAnimationConfig;
297 for (auto const &mutation : mutations) {
298 if (mutation.type == ShadowViewMutation::Type::RemoveDeleteTree) {
299 continue;
300 }
301
302 ShadowView baselineShadowView =
303 (mutation.type == ShadowViewMutation::Type::Delete ||
304 mutation.type == ShadowViewMutation::Type::Remove ||
305 mutation.type == ShadowViewMutation::Type::Update
306 ? mutation.oldChildShadowView
307 : mutation.newChildShadowView);
308 ABI49_0_0React_native_assert(baselineShadowView.tag > 0);
309 bool haveComponentDescriptor =
310 hasComponentDescriptorForShadowView(baselineShadowView);
311
312 // Immediately execute any mutations on a root node
313 if (baselineShadowView.traits.check(
314 ShadowNodeTraits::Trait::RootNodeKind)) {
315 immediateMutations.push_back(mutation);
316 continue;
317 }
318
319 std::optional<ShadowViewMutation> executeMutationImmediately{};
320
321 bool isRemoveReinserted =
322 mutation.type == ShadowViewMutation::Type::Remove &&
323 insertedTags.find(mutation.oldChildShadowView.tag) !=
324 insertedTags.end();
325
326 // Reparenting can result in a node being removed, inserted (moved) and
327 // also deleted and created in the same frame, with the same props etc.
328 // This should eventually be optimized out of the diffing algorithm, but
329 // for now we detect reparenting and prevent the corresponding
330 // Delete/Create instructions from being animated.
331 bool isReparented =
332 reparentedTags.find(baselineShadowView.tag) != reparentedTags.end();
333
334 if (isRemoveReinserted) {
335 movedTags.insert({mutation.oldChildShadowView.tag, mutation});
336 }
337
338 // Inserts that follow a "remove" of the same tag should be treated as
339 // an update (move) animation.
340 bool wasInsertedTagRemoved = false;
341 auto movedIt = movedTags.end();
342 if (mutation.type == ShadowViewMutation::Type::Insert) {
343 // If this is a move, we actually don't want to copy this insert
344 // instruction to animated instructions - we want to
345 // generate an Update mutation for Remove+Insert pairs to animate
346 // the layout.
347 // The corresponding Remove and Insert instructions will instead
348 // be treated as "immediate" instructions.
349 movedIt = movedTags.find(mutation.newChildShadowView.tag);
350 wasInsertedTagRemoved = movedIt != movedTags.end();
351 }
352
353 auto const &mutationConfig =
354 (mutation.type == ShadowViewMutation::Type::Delete ||
355 (mutation.type == ShadowViewMutation::Type::Remove &&
356 !wasInsertedTagRemoved)
357 ? layoutAnimationConfig.deleteConfig
358 : (mutation.type == ShadowViewMutation::Type::Insert &&
359 !wasInsertedTagRemoved
360 ? layoutAnimationConfig.createConfig
361 : layoutAnimationConfig.updateConfig));
362 bool haveConfiguration =
363 mutationConfig.animationType != AnimationType::None;
364
365 // Creates and inserts should also be executed immediately.
366 // Mutations that would otherwise be animated, but have no
367 // configuration, are also executed immediately.
368 if (isRemoveReinserted || !haveConfiguration || isReparented ||
369 mutation.type == ShadowViewMutation::Type::Create ||
370 mutation.type == ShadowViewMutation::Type::Insert) {
371 executeMutationImmediately = mutation;
372
373 // It is possible, especially in the case of "moves", that we have a
374 // sequence of operations like:
375 // UPDATE X
376 // REMOVE X
377 // INSERT X
378 // In these cases, we will have queued up an animation for the UPDATE
379 // and delayed its execution; the REMOVE and INSERT will be executed
380 // first; and then the UPDATE will be animating to/from ShadowViews
381 // that are out-of-sync with what's on the mounting layer. Thus, for
382 // any UPDATE animations already queued up for this tag, we adjust the
383 // "previous" ShadowView.
384 if (mutation.type == ShadowViewMutation::Type::Insert) {
385 for (auto &keyframe : keyFramesToAnimate) {
386 if (keyframe.tag == baselineShadowView.tag) {
387 // If there's already an animation queued up, followed by this
388 // Insert, it *must* be an Update mutation animation. Other
389 // sequences should not be possible.
390 ABI49_0_0React_native_assert(
391 keyframe.type == AnimationConfigurationType::Update);
392
393 // The mutation is an "insert", so it must have a
394 // "newChildShadowView"
395 ABI49_0_0React_native_assert(mutation.newChildShadowView.tag > 0);
396
397 // Those asserts don't run in prod. If there's some edge-case
398 // that we haven't caught yet, we'd crash in debug; make sure we
399 // don't mutate the prevView in prod.
400 if (keyframe.type == AnimationConfigurationType::Update &&
401 mutation.newChildShadowView.tag > 0) {
402 keyframe.viewPrev = mutation.newChildShadowView;
403 }
404 }
405 }
406 } else if (mutation.type == ShadowViewMutation::Type::Remove) {
407 for (auto &keyframe : keyFramesToAnimate) {
408 if (keyframe.tag == baselineShadowView.tag) {
409 // If there's already an animation queued up, followed by this
410 // Insert, it *must* be an Update mutation animation. Other
411 // sequences should not be possible.
412 ABI49_0_0React_native_assert(
413 keyframe.type == AnimationConfigurationType::Update);
414
415 // The mutation is a "remove", so it must have a
416 // "oldChildShadowView"
417 ABI49_0_0React_native_assert(mutation.oldChildShadowView.tag > 0);
418
419 // Those asserts don't run in prod. If there's some edge-case
420 // that we haven't caught yet, we'd crash in debug; make sure we
421 // don't mutate the prevView in prod.
422 // Since normally the UPDATE would have been executed first and
423 // now it's deferred, we need to change the `oldChildShadowView`
424 // that is being referenced by the REMOVE mutation.
425 if (keyframe.type == AnimationConfigurationType::Update &&
426 mutation.oldChildShadowView.tag > 0) {
427 executeMutationImmediately =
428 ShadowViewMutation::RemoveMutation(
429 mutation.parentShadowView,
430 keyframe.viewPrev,
431 mutation.index);
432 }
433 }
434 }
435 }
436 }
437
438 // Deletes, non-move inserts, updates get animated
439 if (!wasInsertedTagRemoved && !isRemoveReinserted && !isReparented &&
440 haveConfiguration &&
441 mutation.type != ShadowViewMutation::Type::Create) {
442 ShadowView viewStart = ShadowView(
443 mutation.type == ShadowViewMutation::Type::Insert
444 ? mutation.newChildShadowView
445 : mutation.oldChildShadowView);
446 ABI49_0_0React_native_assert(viewStart.tag > 0);
447 ShadowView viewFinal = ShadowView(
448 mutation.type == ShadowViewMutation::Type::Update
449 ? mutation.newChildShadowView
450 : viewStart);
451 ABI49_0_0React_native_assert(viewFinal.tag > 0);
452 ShadowView parent = mutation.parentShadowView;
453 ABI49_0_0React_native_assert(
454 parent.tag > 0 ||
455 mutation.type == ShadowViewMutation::Type::Update ||
456 mutation.type == ShadowViewMutation::Type::Delete);
457 Tag tag = viewStart.tag;
458
459 AnimationKeyFrame keyFrame{};
460 if (mutation.type == ShadowViewMutation::Type::Insert) {
461 if (mutationConfig.animationProperty ==
462 AnimationProperty::Opacity &&
463 haveComponentDescriptor) {
464 auto props =
465 getComponentDescriptorForShadowView(baselineShadowView)
466 .cloneProps(propsParserContext, viewStart.props, {});
467
468 if (baselineShadowView.traits.check(
469 ShadowNodeTraits::Trait::ViewKind)) {
470 auto const &viewProps =
471 *std::static_pointer_cast<ViewProps const>(props);
472 const_cast<ViewProps &>(viewProps).opacity = 0;
473 }
474
475 ABI49_0_0React_native_assert(props != nullptr);
476 if (props != nullptr) {
477 viewStart.props = props;
478 }
479 }
480 bool isScaleX =
481 mutationConfig.animationProperty == AnimationProperty::ScaleX ||
482 mutationConfig.animationProperty == AnimationProperty::ScaleXY;
483 bool isScaleY =
484 mutationConfig.animationProperty == AnimationProperty::ScaleY ||
485 mutationConfig.animationProperty == AnimationProperty::ScaleXY;
486 if ((isScaleX || isScaleY) && haveComponentDescriptor) {
487 auto props =
488 getComponentDescriptorForShadowView(baselineShadowView)
489 .cloneProps(propsParserContext, viewStart.props, {});
490 if (baselineShadowView.traits.check(
491 ShadowNodeTraits::Trait::ViewKind)) {
492 auto const &viewProps =
493 *std::static_pointer_cast<ViewProps const>(props);
494 const_cast<ViewProps &>(viewProps).transform =
495 Transform::Scale(isScaleX ? 0 : 1, isScaleY ? 0 : 1, 1);
496 }
497
498 ABI49_0_0React_native_assert(props != nullptr);
499 if (props != nullptr) {
500 viewStart.props = props;
501 }
502 }
503
504 PrintMutationInstruction(
505 "Setting up animation KeyFrame for INSERT mutation (Create animation)",
506 mutation);
507
508 keyFrame = AnimationKeyFrame{
509 /* .finalMutationsForKeyFrame = */ {},
510 /* .type = */ AnimationConfigurationType::Create,
511 /* .tag = */ tag,
512 /* .parentView = */ parent,
513 /* .viewStart = */ viewStart,
514 /* .viewEnd = */ viewFinal,
515 /* .viewPrev = */ baselineShadowView,
516 /* .initialProgress = */ 0};
517 } else if (mutation.type == ShadowViewMutation::Type::Delete) {
518 // This is just for assertion purposes.
519 // The NDEBUG check here is to satisfy the compiler in certain environments
520 // complaining about correspondingRemoveIt being unused.
521 #ifdef ABI49_0_0REACT_NATIVE_DEBUG
522 #ifndef NDEBUG
523 // This block is temporarily disabled to fix some internal builds.
524 // In some build configurations, we get a compiler error that
525 // `correspondingRemoveIt` is unused.
526 /* Tag deleteTag = mutation.oldChildShadowView.tag;
527 auto correspondingRemoveIt = std::find_if(
528 mutations.begin(),
529 mutations.end(),
530 [&deleteTag](auto &mutation) {
531 return mutation.type == ShadowViewMutation::Type::Remove &&
532 mutation.oldChildShadowView.tag == deleteTag;
533 });
534 ABI49_0_0React_native_assert(correspondingRemoveIt != mutations.end());
535 */
536 #endif
537 #endif
538 continue;
539 } else if (mutation.type == ShadowViewMutation::Type::Update) {
540 viewFinal = ShadowView(mutation.newChildShadowView);
541
542 PrintMutationInstruction(
543 "Setting up animation KeyFrame for UPDATE mutation (Update animation)",
544 mutation);
545
546 keyFrame = AnimationKeyFrame{
547 /* .finalMutationsForKeyFrame = */ {mutation},
548 /* .type = */ AnimationConfigurationType::Update,
549 /* .tag = */ tag,
550 /* .parentView = */ parent,
551 /* .viewStart = */ viewStart,
552 /* .viewEnd = */ viewFinal,
553 /* .viewPrev = */ baselineShadowView,
554 /* .initialProgress = */ 0};
555 } else {
556 // This should just be "Remove" instructions that are not animated
557 // (either this is a "move", or there's a corresponding "Delete"
558 // that is animated).
559 ABI49_0_0React_native_assert(
560 mutation.type == ShadowViewMutation::Type::Remove);
561
562 Tag removeTag = mutation.oldChildShadowView.tag;
563 auto correspondingInsertIt = std::find_if(
564 mutations.begin(),
565 mutations.end(),
566 [&removeTag](auto &mutation) {
567 return mutation.type == ShadowViewMutation::Type::Insert &&
568 mutation.newChildShadowView.tag == removeTag;
569 });
570 if (correspondingInsertIt == mutations.end()) {
571 // This is a REMOVE not paired with an INSERT (move), so it must
572 // be paired with a DELETE.
573 auto correspondingDeleteIt = std::find_if(
574 mutations.begin(),
575 mutations.end(),
576 [&removeTag](auto &mutation) {
577 return mutation.type == ShadowViewMutation::Type::Delete &&
578 mutation.oldChildShadowView.tag == removeTag;
579 });
580 ABI49_0_0React_native_assert(correspondingDeleteIt != mutations.end());
581
582 auto deleteMutation = *correspondingDeleteIt;
583
584 if (mutationConfig.animationProperty ==
585 AnimationProperty::Opacity &&
586 haveComponentDescriptor) {
587 auto props =
588 getComponentDescriptorForShadowView(baselineShadowView)
589 .cloneProps(propsParserContext, viewFinal.props, {});
590
591 if (baselineShadowView.traits.check(
592 ShadowNodeTraits::Trait::ViewKind)) {
593 auto const &viewProps =
594 *std::static_pointer_cast<ViewProps const>(props);
595 const_cast<ViewProps &>(viewProps).opacity = 0;
596 }
597
598 ABI49_0_0React_native_assert(props != nullptr);
599 if (props != nullptr) {
600 viewFinal.props = props;
601 }
602 }
603 bool isScaleX = mutationConfig.animationProperty ==
604 AnimationProperty::ScaleX ||
605 mutationConfig.animationProperty ==
606 AnimationProperty::ScaleXY;
607 bool isScaleY = mutationConfig.animationProperty ==
608 AnimationProperty::ScaleY ||
609 mutationConfig.animationProperty ==
610 AnimationProperty::ScaleXY;
611 if ((isScaleX || isScaleY) && haveComponentDescriptor) {
612 auto props =
613 getComponentDescriptorForShadowView(baselineShadowView)
614 .cloneProps(propsParserContext, viewFinal.props, {});
615
616 if (baselineShadowView.traits.check(
617 ShadowNodeTraits::Trait::ViewKind)) {
618 auto const &viewProps =
619 *std::static_pointer_cast<ViewProps const>(props);
620 const_cast<ViewProps &>(viewProps).transform =
621 Transform::Scale(isScaleX ? 0 : 1, isScaleY ? 0 : 1, 1);
622 }
623
624 ABI49_0_0React_native_assert(props != nullptr);
625 if (props != nullptr) {
626 viewFinal.props = props;
627 }
628 }
629
630 PrintMutationInstruction(
631 "Setting up animation KeyFrame for REMOVE mutation (Delete animation)",
632 mutation);
633
634 keyFrame = AnimationKeyFrame{
635 /* .finalMutationsForKeyFrame */ {mutation, deleteMutation},
636 /* .type */ AnimationConfigurationType::Delete,
637 /* .tag */ tag,
638 /* .parentView */ parent,
639 /* .viewStart */ viewStart,
640 /* .viewEnd */ viewFinal,
641 /* .viewPrev */ baselineShadowView,
642 /* .initialProgress */ 0};
643 } else {
644 PrintMutationInstruction(
645 "Executing Remove Immediately, due to reordering operation",
646 mutation);
647 immediateMutations.push_back(mutation);
648 continue;
649 }
650 }
651
652 // Handle conflicting animations
653 for (auto &conflictingKeyFrame : conflictingAnimations) {
654 auto const &conflictingMutationBaselineShadowView =
655 conflictingKeyFrame.viewStart;
656
657 // We've found a conflict.
658 if (conflictingMutationBaselineShadowView.tag == tag) {
659 conflictingKeyFrame.generateFinalSyntheticMutations = false;
660
661 // Do NOT update viewStart for a CREATE animation.
662 if (keyFrame.type == AnimationConfigurationType::Create) {
663 break;
664 }
665
666 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
667 LOG(ERROR)
668 << "Due to conflict, replacing 'viewStart' of animated keyframe: ["
669 << conflictingKeyFrame.viewPrev.tag << "] with ##"
670 << std::hash<ShadowView>{}(conflictingKeyFrame.viewPrev);
671 #endif
672 // Pick a Prop or layout property, depending on the current
673 // animation configuration. Figure out how much progress we've
674 // already made in the current animation, and start the animation
675 // from this point.
676 keyFrame.viewPrev = conflictingKeyFrame.viewPrev;
677 keyFrame.viewStart = conflictingKeyFrame.viewPrev;
678 ABI49_0_0React_native_assert(keyFrame.viewStart.tag > 0);
679 keyFrame.initialProgress = 0;
680
681 // We're guaranteed that a tag only has one animation associated
682 // with it, so we can break here. If we support multiple
683 // animations and animation curves over the same tag in the
684 // future, this will need to be modified to support that.
685 break;
686 }
687 }
688
689 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
690 LOG(ERROR) << "Checking validity of keyframe: ["
691 << keyFrame.viewStart.tag << "] [" << keyFrame.viewEnd.tag
692 << "] [" << keyFrame.viewPrev.tag
693 << "] animation type: " << (int)keyFrame.type;
694 #endif
695 ABI49_0_0React_native_assert(keyFrame.viewStart.tag > 0);
696 ABI49_0_0React_native_assert(keyFrame.viewEnd.tag > 0);
697 ABI49_0_0React_native_assert(keyFrame.viewPrev.tag > 0);
698 keyFramesToAnimate.push_back(keyFrame);
699 }
700
701 if (executeMutationImmediately.has_value()) {
702 PrintMutationInstruction(
703 "Queue Up For Immediate Execution", *executeMutationImmediately);
704 immediateMutations.push_back(*executeMutationImmediately);
705 }
706 }
707
708 #ifdef ABI49_0_0RN_SHADOW_TREE_INTROSPECTION
709 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
710 {
711 int idx = 0;
712 for (auto &mutation : immediateMutations) {
713 PrintMutationInstruction(
714 std::string("IMMEDIATE list: ") + std::to_string(idx) + "/" +
715 std::to_string(immediateMutations.size()),
716 mutation);
717 idx++;
718 }
719 }
720
721 {
722 int idx = 0;
723 for (const auto &keyframe : keyFramesToAnimate) {
724 for (const auto &finalMutationForKeyFrame :
725 keyframe.finalMutationsForKeyFrame) {
726 PrintMutationInstruction(
727 std::string("FINAL list: ") + std::to_string(idx) + "/" +
728 std::to_string(keyFramesToAnimate.size()),
729 finalMutationForKeyFrame);
730 }
731 idx++;
732 }
733 }
734 #endif
735 #endif
736
737 auto finalConflictingMutations = ShadowViewMutationList{};
738 for (auto &keyFrame : conflictingAnimations) {
739 // Special-case: if the next conflicting animation contain "delete",
740 // while the final mutation has the same tag with "create", we should
741 // remove both the delete and create as they have no effect when
742 // combined in the same frame. The Fabric mount layer assumes no such
743 // combinations in the final mutations either.
744 if (reduceDeleteCreateMutation_) {
745 for (auto itMutation = immediateMutations.begin();
746 itMutation != immediateMutations.end();) {
747 auto &mutation = *itMutation;
748 bool hasCreateMutationDeletedWithSameTag = false;
749 if (mutation.newChildShadowView.tag == keyFrame.tag &&
750 mutation.type == ShadowViewMutation::Create) {
751 for (auto itKeyFrame = keyFrame.finalMutationsForKeyFrame.begin();
752 itKeyFrame != keyFrame.finalMutationsForKeyFrame.end();) {
753 auto &conflictFinalMutation = *itKeyFrame;
754 if (conflictFinalMutation.type == ShadowViewMutation::Delete) {
755 itKeyFrame =
756 keyFrame.finalMutationsForKeyFrame.erase(itKeyFrame);
757 hasCreateMutationDeletedWithSameTag = true;
758 break;
759 } else {
760 itKeyFrame++;
761 }
762 }
763 }
764
765 if (hasCreateMutationDeletedWithSameTag) {
766 itMutation = immediateMutations.erase(itMutation);
767 } else {
768 itMutation++;
769 }
770 }
771 }
772
773 // Special-case: if we have some (1) ongoing UPDATE animation,
774 // (2) it conflicted with a new MOVE operation (REMOVE+INSERT)
775 // without another corresponding UPDATE, we should re-queue the
776 // keyframe so that its position/props don't suddenly "jump".
777 if (keyFrame.type == AnimationConfigurationType::Update) {
778 auto movedIt = movedTags.find(keyFrame.tag);
779 if (movedIt != movedTags.end()) {
780 auto newKeyFrameForUpdate = std::find_if(
781 keyFramesToAnimate.begin(),
782 keyFramesToAnimate.end(),
783 [&](auto const &newKeyFrame) {
784 return newKeyFrame.type ==
785 AnimationConfigurationType::Update &&
786 newKeyFrame.tag == keyFrame.tag;
787 });
788 if (newKeyFrameForUpdate == keyFramesToAnimate.end()) {
789 keyFrame.invalidated = false;
790
791 // The animation will continue from the current position - we
792 // restart viewStart to make sure there are no sudden jumps
793 keyFrame.viewStart = keyFrame.viewPrev;
794
795 // Find the insert mutation that conflicted with this update
796 for (auto &mutation : immediateMutations) {
797 if (mutation.newChildShadowView.tag == keyFrame.tag &&
798 (mutation.type == ShadowViewMutation::Insert ||
799 mutation.type == ShadowViewMutation::Create)) {
800 keyFrame.viewPrev = mutation.newChildShadowView;
801 keyFrame.viewEnd = mutation.newChildShadowView;
802 }
803 }
804 keyFramesToAnimate.push_back(keyFrame);
805 continue;
806 }
807 }
808 }
809
810 // If the "final" mutation is already accounted for, by previously
811 // setting the correct "viewPrev" of the next conflicting animation, we
812 // don't want to queue up any final UPDATE mutations here.
813 bool shouldGenerateSyntheticMutations =
814 keyFrame.generateFinalSyntheticMutations;
815 auto numFinalMutations = keyFrame.finalMutationsForKeyFrame.size();
816 bool onlyMutationIsUpdate =
817 (numFinalMutations == 1 &&
818 keyFrame.finalMutationsForKeyFrame[0].type ==
819 ShadowViewMutation::Update);
820 if (!shouldGenerateSyntheticMutations &&
821 (numFinalMutations == 0 || onlyMutationIsUpdate)) {
822 continue;
823 }
824
825 queueFinalMutationsForCompletedKeyFrame(
826 keyFrame,
827 finalConflictingMutations,
828 true,
829 "KeyFrameManager: Finished Conflicting Keyframe");
830 }
831
832 // Make sure that all operations execute in the proper order, since
833 // conflicting animations are not sorted in any reasonable way.
834 std::stable_sort(
835 finalConflictingMutations.begin(),
836 finalConflictingMutations.end(),
837 &shouldFirstComeBeforeSecondMutation);
838
839 std::stable_sort(
840 immediateMutations.begin(),
841 immediateMutations.end(),
842 &shouldFirstComeBeforeSecondRemovesOnly);
843
844 animation.keyFrames = keyFramesToAnimate;
845 inflightAnimations_.push_back(std::move(animation));
846
847 // At this point, we have the following information and knowledge graph:
848 // Knowledge Graph:
849 // [ImmediateMutations] -> assumes [FinalConflicting], [FrameDelayed],
850 // [Delayed] already executed [FrameDelayed] -> assumes
851 // [FinalConflicting], [Delayed] already executed [FinalConflicting] ->
852 // is adjusted based on [Delayed], no dependency on [FinalConflicting],
853 // [FrameDelayed] [Delayed] -> assumes [FinalConflicting],
854 // [ImmediateMutations] not executed yet
855
856 // Adjust [Delayed] based on [FinalConflicting]
857 // Knowledge Graph:
858 // [ImmediateMutations] -> assumes [FinalConflicting], [FrameDelayed],
859 // [Delayed] already executed [FrameDelayed] -> assumes
860 // [FinalConflicting], [Delayed] already executed [FinalConflicting] ->
861 // is adjusted based on [Delayed], no dependency on [FinalConflicting],
862 // [FrameDelayed] [Delayed] -> adjusted for [FinalConflicting]; assumes
863 // [ImmediateMutations] not executed yet
864 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
865 LOG(ERROR) << "Adjust [Delayed] based on [FinalConflicting]";
866 #endif
867 for (auto &mutation : finalConflictingMutations) {
868 if (mutation.type == ShadowViewMutation::Type::Insert ||
869 mutation.type == ShadowViewMutation::Type::Remove) {
870 adjustDelayedMutationIndicesForMutation(surfaceId, mutation, true);
871 }
872 }
873
874 // Adjust [FrameDelayed] based on [Delayed]
875 // Knowledge Graph:
876 // [ImmediateExecutions] -> assumes [FinalConflicting], [Delayed],
877 // [FrameDelayed] already executed [FrameDelayed] -> adjusted for
878 // [Delayed]; assumes [FinalConflicting] already executed
879 // [FinalConflicting] -> is adjusted based on [Delayed], no dependency
880 // on [FinalConflicting], [FrameDelayed] [Delayed] -> adjusted for
881 // [FinalConflicting]; assumes [ImmediateExecutions] not executed yet
882 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
883 LOG(ERROR) << "Adjust [FrameDelayed] based on [Delayed]";
884 #endif
885 for (auto &keyframe : inflightAnimations_.back().keyFrames) {
886 for (auto &finalMutation : keyframe.finalMutationsForKeyFrame) {
887 if (finalMutation.type == ShadowViewMutation::Type::Insert ||
888 finalMutation.type == ShadowViewMutation::Type::Remove) {
889 // When adjusting, skip adjusting against last animation - because
890 // all `mutation`s here come from the last animation, so we can't
891 // adjust a batch against itself.
892 adjustImmediateMutationIndicesForDelayedMutations(
893 surfaceId, finalMutation, true);
894 }
895 }
896 }
897
898 // Adjust [ImmediateExecutions] based on [Delayed]
899 // Knowledge Graph:
900 // [ImmediateExecutions] -> adjusted for [FrameDelayed], [Delayed];
901 // assumes [FinalConflicting] already executed [FrameDelayed] ->
902 // adjusted for [Delayed]; assumes [FinalConflicting] already executed
903 // [FinalConflicting] -> is adjusted based on [Delayed], no dependency
904 // on [FinalConflicting], [FrameDelayed] [Delayed] -> adjusted for
905 // [FinalConflicting]; assumes [ImmediateExecutions] not executed yet
906 //
907 // THEN,
908 // Adjust [Delayed] based on [ImmediateExecutions] and
909 // [FinalConflicting] Knowledge Graph: [ImmediateExecutions] -> adjusted
910 // for [FrameDelayed], [Delayed]; assumes [FinalConflicting] already
911 // executed [FrameDelayed] -> adjusted for [Delayed]; assumes
912 // [FinalConflicting] already executed [FinalConflicting] -> is adjusted
913 // based on [Delayed], no dependency on [FinalConflicting],
914 // [FrameDelayed] [Delayed] -> adjusted for [FinalConflicting],
915 // [ImmediateExecutions]
916 //
917 // We do these in the same loop because each immediate execution is
918 // impacted by each delayed mutation, and also can impact each delayed
919 // mutation, and these effects compound.
920 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
921 LOG(ERROR)
922 << "Adjust each [ImmediateExecution] based on [Delayed] and [Delayed] based on each [ImmediateExecution]";
923 #endif
924 for (auto &mutation : immediateMutations) {
925 // Note: when adjusting [ImmediateExecutions] based on [FrameDelayed],
926 // we need only adjust Inserts. Since inserts are executed
927 // highest-index-first, lower indices being delayed does not impact
928 // the higher-index removals; and conversely, higher indices being
929 // delayed cannot impact lower index removal, regardless of order.
930 if (mutation.type == ShadowViewMutation::Type::Insert ||
931 mutation.type == ShadowViewMutation::Type::Remove) {
932 adjustImmediateMutationIndicesForDelayedMutations(
933 surfaceId,
934 mutation,
935 mutation.type == ShadowViewMutation::Type::Remove);
936 // Here we need to adjust both Delayed and FrameDelayed mutations.
937 // Delayed Removes can be impacted by non-delayed Inserts from the
938 // same frame.
939 adjustDelayedMutationIndicesForMutation(surfaceId, mutation);
940 }
941 }
942
943 // If the knowledge graph progression above is correct, it is now safe
944 // to execute finalConflictingMutations and immediateMutations in that
945 // order, and to queue the delayed animations from this frame.
946 //
947 // Execute the conflicting, delayed operations immediately. Any UPDATE
948 // operations that smoothly transition into another animation will be
949 // overridden by generated UPDATE operations at the end of the list, and
950 // we want any REMOVE or DELETE operations to execute immediately.
951 // Additionally, this should allow us to avoid performing index
952 // adjustment between this list of conflicting animations and the batch
953 // we're about to execute.
954 finalConflictingMutations.insert(
955 finalConflictingMutations.end(),
956 immediateMutations.begin(),
957 immediateMutations.end());
958 mutations = finalConflictingMutations;
959 } /* if (currentAnimation) */
960 else {
961 // If there's no "next" animation, make sure we queue up "final"
962 // operations from all ongoing, conflicting animations.
963 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
964 LOG(ERROR) << "No Animation: Queue up final conflicting animations";
965 #endif
966 ShadowViewMutationList finalMutationsForConflictingAnimations{};
967 for (auto const &keyFrame : conflictingAnimations) {
968 queueFinalMutationsForCompletedKeyFrame(
969 keyFrame,
970 finalMutationsForConflictingAnimations,
971 true,
972 "Conflict with non-animated mutation");
973 }
974
975 // Make sure that all operations execute in the proper order.
976 // REMOVE operations with highest indices must operate first.
977 std::stable_sort(
978 finalMutationsForConflictingAnimations.begin(),
979 finalMutationsForConflictingAnimations.end(),
980 &shouldFirstComeBeforeSecondMutation);
981
982 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
983 LOG(ERROR)
984 << "No Animation: Adjust delayed mutations based on all finalMutationsForConflictingAnimations";
985 #endif
986 for (auto const &mutation : finalMutationsForConflictingAnimations) {
987 if (mutation.type == ShadowViewMutation::Type::Remove ||
988 mutation.type == ShadowViewMutation::Type::Insert) {
989 adjustDelayedMutationIndicesForMutation(surfaceId, mutation);
990 }
991 }
992
993 // The ShadowTree layer doesn't realize that certain operations have
994 // been delayed, so we must adjust all Remove and Insert operations
995 // based on what else has been deferred, whether we are executing this
996 // immediately or later.
997 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
998 LOG(ERROR)
999 << "No Animation: Adjust mutations based on remaining delayed mutations / adjust delayed, based on each";
1000 #endif
1001 for (auto &mutation : mutations) {
1002 if (mutation.type == ShadowViewMutation::Type::Remove ||
1003 mutation.type == ShadowViewMutation::Type::Insert) {
1004 adjustImmediateMutationIndicesForDelayedMutations(
1005 surfaceId, mutation);
1006 adjustDelayedMutationIndicesForMutation(surfaceId, mutation);
1007 }
1008 }
1009
1010 // Append mutations to this list and swap - so that the final
1011 // conflicting mutations happen before any other mutations
1012 finalMutationsForConflictingAnimations.insert(
1013 finalMutationsForConflictingAnimations.end(),
1014 mutations.begin(),
1015 mutations.end());
1016 mutations = finalMutationsForConflictingAnimations;
1017 }
1018 } // if (mutations)
1019
1020 // We never commit a different root or modify anything -
1021 // we just send additional mutations to the mounting layer until the
1022 // animations are finished and the mounting layer (view) represents exactly
1023 // what is in the most recent shadow tree
1024 // Add animation mutations to the end of our existing mutations list in this
1025 // function.
1026 ShadowViewMutationList mutationsForAnimation{};
1027 animationMutationsForFrame(surfaceId, mutationsForAnimation, now);
1028
1029 // If any delayed removes were executed, update remaining delayed keyframes
1030 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
1031 LOG(ERROR)
1032 << "Adjust all delayed mutations based on final mutations generated by animation driver";
1033 #endif
1034 for (auto const &mutation : mutationsForAnimation) {
1035 if (mutation.type == ShadowViewMutation::Type::Remove) {
1036 adjustDelayedMutationIndicesForMutation(surfaceId, mutation);
1037 }
1038 }
1039
1040 mutations.insert(
1041 mutations.end(),
1042 mutationsForAnimation.begin(),
1043 mutationsForAnimation.end());
1044
1045 // DEBUG ONLY: list existing inflight animations
1046 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
1047 LOG(ERROR) << "FINISHING DISPLAYING ONGOING inflightAnimations_!";
1048 int i = 0;
1049 int j = 0;
1050 for (auto const &inflightAnimation : inflightAnimations_) {
1051 i++;
1052 j = 0;
1053 if (inflightAnimation.completed) {
1054 continue;
1055 }
1056 for (auto &keyframe : inflightAnimation.keyFrames) {
1057 j++;
1058 if (keyframe.invalidated) {
1059 continue;
1060 }
1061 for (auto const &finalMutation : keyframe.finalMutationsForKeyFrame) {
1062 if (!finalMutation.mutatedViewIsVirtual()) {
1063 std::string msg = "Animation " + std::to_string(i) + " keyframe " +
1064 std::to_string(j) + ": Final Animation";
1065 PrintMutationInstruction(msg, finalMutation);
1066 }
1067 }
1068 }
1069 }
1070 LOG(ERROR) << "FINISHING DONE DISPLAYING ONGOING inflightAnimations_!";
1071 #endif
1072
1073 // Signal to delegate if all animations are complete, or if we were not
1074 // animating anything and now some animation exists.
1075 if (inflightAnimationsExistInitially && inflightAnimations_.empty()) {
1076 std::lock_guard<std::mutex> lock(layoutAnimationStatusDelegateMutex_);
1077 if (layoutAnimationStatusDelegate_ != nullptr) {
1078 layoutAnimationStatusDelegate_->onAllAnimationsComplete();
1079 }
1080 } else if (
1081 !inflightAnimationsExistInitially && !inflightAnimations_.empty()) {
1082 std::lock_guard<std::mutex> lock(layoutAnimationStatusDelegateMutex_);
1083 if (layoutAnimationStatusDelegate_ != nullptr) {
1084 layoutAnimationStatusDelegate_->onAnimationStarted();
1085 }
1086 }
1087
1088 return MountingTransaction{
1089 surfaceId, transactionNumber, std::move(mutations), telemetry};
1090 }
1091
uiManagerDidConfigureNextLayoutAnimation(LayoutAnimation layoutAnimation) const1092 void LayoutAnimationKeyFrameManager::uiManagerDidConfigureNextLayoutAnimation(
1093 LayoutAnimation layoutAnimation) const {
1094 currentAnimation_ = std::optional<LayoutAnimation>{layoutAnimation};
1095 }
1096
setLayoutAnimationStatusDelegate(LayoutAnimationStatusDelegate * delegate) const1097 void LayoutAnimationKeyFrameManager::setLayoutAnimationStatusDelegate(
1098 LayoutAnimationStatusDelegate *delegate) const {
1099 std::lock_guard<std::mutex> lock(layoutAnimationStatusDelegateMutex_);
1100 layoutAnimationStatusDelegate_ = delegate;
1101 }
1102
setClockNow(std::function<uint64_t ()> now)1103 void LayoutAnimationKeyFrameManager::setClockNow(
1104 std::function<uint64_t()> now) {
1105 now_ = std::move(now);
1106 }
1107
1108 #pragma mark - Protected
1109
hasComponentDescriptorForShadowView(ShadowView const & shadowView) const1110 bool LayoutAnimationKeyFrameManager::hasComponentDescriptorForShadowView(
1111 ShadowView const &shadowView) const {
1112 return componentDescriptorRegistry_->hasComponentDescriptorAt(
1113 shadowView.componentHandle);
1114 }
1115
1116 ComponentDescriptor const &
getComponentDescriptorForShadowView(ShadowView const & shadowView) const1117 LayoutAnimationKeyFrameManager::getComponentDescriptorForShadowView(
1118 ShadowView const &shadowView) const {
1119 return componentDescriptorRegistry_->at(shadowView.componentHandle);
1120 }
1121
createInterpolatedShadowView(Float progress,ShadowView const & startingView,ShadowView const & finalView) const1122 ShadowView LayoutAnimationKeyFrameManager::createInterpolatedShadowView(
1123 Float progress,
1124 ShadowView const &startingView,
1125 ShadowView const &finalView) const {
1126 ABI49_0_0React_native_assert(startingView.tag > 0);
1127 ABI49_0_0React_native_assert(finalView.tag > 0);
1128 if (!hasComponentDescriptorForShadowView(startingView)) {
1129 LOG(ERROR) << "No ComponentDescriptor for ShadowView being animated: ["
1130 << startingView.tag << "]";
1131 ABI49_0_0React_native_assert(false);
1132 return finalView;
1133 }
1134
1135 ComponentDescriptor const &componentDescriptor =
1136 getComponentDescriptorForShadowView(startingView);
1137
1138 // Base the mutated view on the finalView, so that the following stay
1139 // consistent:
1140 // - state
1141 // - eventEmitter
1142 // For now, we do not allow interpolation of state. And we probably never
1143 // will, so make sure we always keep the mounting layer consistent with the
1144 // "final" state.
1145 auto mutatedShadowView = ShadowView(finalView);
1146 ABI49_0_0React_native_assert(mutatedShadowView.tag > 0);
1147
1148 ABI49_0_0React_native_assert(startingView.props != nullptr);
1149 ABI49_0_0React_native_assert(finalView.props != nullptr);
1150 if (startingView.props == nullptr || finalView.props == nullptr) {
1151 return finalView;
1152 }
1153
1154 // Animate opacity or scale/transform
1155 PropsParserContext propsParserContext{
1156 finalView.surfaceId, *contextContainer_};
1157 mutatedShadowView.props = componentDescriptor.interpolateProps(
1158 propsParserContext, progress, startingView.props, finalView.props);
1159 ABI49_0_0React_native_assert(mutatedShadowView.props != nullptr);
1160 if (mutatedShadowView.props == nullptr) {
1161 return finalView;
1162 }
1163
1164 // Interpolate LayoutMetrics
1165 LayoutMetrics const &finalLayoutMetrics = finalView.layoutMetrics;
1166 LayoutMetrics const &baselineLayoutMetrics = startingView.layoutMetrics;
1167 LayoutMetrics interpolatedLayoutMetrics = finalLayoutMetrics;
1168 interpolatedLayoutMetrics.frame.origin.x = interpolateFloats(
1169 progress,
1170 baselineLayoutMetrics.frame.origin.x,
1171 finalLayoutMetrics.frame.origin.x);
1172 interpolatedLayoutMetrics.frame.origin.y = interpolateFloats(
1173 progress,
1174 baselineLayoutMetrics.frame.origin.y,
1175 finalLayoutMetrics.frame.origin.y);
1176 interpolatedLayoutMetrics.frame.size.width = interpolateFloats(
1177 progress,
1178 baselineLayoutMetrics.frame.size.width,
1179 finalLayoutMetrics.frame.size.width);
1180 interpolatedLayoutMetrics.frame.size.height = interpolateFloats(
1181 progress,
1182 baselineLayoutMetrics.frame.size.height,
1183 finalLayoutMetrics.frame.size.height);
1184 mutatedShadowView.layoutMetrics = interpolatedLayoutMetrics;
1185
1186 return mutatedShadowView;
1187 }
1188
callCallback(LayoutAnimationCallbackWrapper const & callback) const1189 void LayoutAnimationKeyFrameManager::callCallback(
1190 LayoutAnimationCallbackWrapper const &callback) const {
1191 runtimeExecutor_(
1192 [callback](jsi::Runtime &runtime) { callback.call(runtime); });
1193 }
1194
queueFinalMutationsForCompletedKeyFrame(AnimationKeyFrame const & keyframe,ShadowViewMutation::List & mutationsList,bool interrupted,const std::string &) const1195 void LayoutAnimationKeyFrameManager::queueFinalMutationsForCompletedKeyFrame(
1196 AnimationKeyFrame const &keyframe,
1197 ShadowViewMutation::List &mutationsList,
1198 bool interrupted,
1199 const std::string & /*logPrefix*/) const {
1200 if (!keyframe.finalMutationsForKeyFrame.empty()) {
1201 // TODO: modularize this segment, it is repeated 2x in KeyFrameManager
1202 // as well.
1203 ShadowView prev = keyframe.viewPrev;
1204 for (auto const &finalMutation : keyframe.finalMutationsForKeyFrame) {
1205 PrintMutationInstruction(
1206 logPrefix + ": Queuing up Final Mutation:", finalMutation);
1207 // Copy so that if something else mutates the inflight animations,
1208 // it won't change this mutation after this point.
1209 switch (finalMutation.type) {
1210 // For CREATE/INSERT this will contain CREATE, INSERT in that order.
1211 // For REMOVE/DELETE, same.
1212 case ShadowViewMutation::Type::Create:
1213 mutationsList.push_back(ShadowViewMutation::CreateMutation(
1214 finalMutation.newChildShadowView));
1215 break;
1216 case ShadowViewMutation::Type::Delete:
1217 mutationsList.push_back(ShadowViewMutation::DeleteMutation(prev));
1218 break;
1219 case ShadowViewMutation::Type::Insert:
1220 mutationsList.push_back(ShadowViewMutation::InsertMutation(
1221 finalMutation.parentShadowView,
1222 finalMutation.newChildShadowView,
1223 finalMutation.index));
1224 break;
1225 case ShadowViewMutation::Type::Remove:
1226 mutationsList.push_back(ShadowViewMutation::RemoveMutation(
1227 finalMutation.parentShadowView, prev, finalMutation.index));
1228 break;
1229 case ShadowViewMutation::Type::RemoveDeleteTree:
1230 // Note: Currently, there is a guarantee that if RemoveDeleteTree
1231 // operations are generated, we /also/ generate corresponding
1232 // Remove/Delete operations that are marked as "redundant".
1233 // LayoutAnimations will process the redundant operations here, and
1234 // ignore this mega-op. In the future for perf reasons it would be
1235 // nice to remove the redundant operations entirely but we would need
1236 // to find a way to make the RemoveDeleteTree operation work with
1237 // LayoutAnimations (that might not be possible).
1238 break;
1239 case ShadowViewMutation::Type::Update:
1240 mutationsList.push_back(ShadowViewMutation::UpdateMutation(
1241 prev,
1242 finalMutation.newChildShadowView,
1243 finalMutation.parentShadowView));
1244 break;
1245 }
1246 if (finalMutation.newChildShadowView.tag > 0) {
1247 prev = finalMutation.newChildShadowView;
1248 }
1249 }
1250 } else {
1251 // If there's no final mutation associated, create a mutation that
1252 // corresponds to the animation being 100% complete. This is
1253 // important for, for example, INSERT mutations being animated from
1254 // opacity 0 to 1. If the animation is interrupted we must force the
1255 // View to be at opacity 1. For Android - since it passes along only
1256 // deltas, not an entire bag of props - generate an "animation"
1257 // frame corresponding to a final update for this view. Only then,
1258 // generate an update that will cause the ShadowTree to be
1259 // consistent with the Mounting layer by passing viewEnd,
1260 // unmodified, to the mounting layer. This helps with, for example,
1261 // opacity animations.
1262 // This is necessary for INSERT (create) and UPDATE (update) mutations, but
1263 // not REMOVE/DELETE mutations ("delete" animations).
1264 if (interrupted) {
1265 auto mutatedShadowView =
1266 createInterpolatedShadowView(1, keyframe.viewStart, keyframe.viewEnd);
1267 auto generatedPenultimateMutation = ShadowViewMutation::UpdateMutation(
1268 keyframe.viewPrev, mutatedShadowView, keyframe.parentView);
1269 ABI49_0_0React_native_assert(
1270 generatedPenultimateMutation.oldChildShadowView.tag > 0);
1271 ABI49_0_0React_native_assert(
1272 generatedPenultimateMutation.newChildShadowView.tag > 0);
1273 PrintMutationInstruction(
1274 "Queueing up penultimate mutation instruction - synthetic",
1275 generatedPenultimateMutation);
1276 mutationsList.push_back(generatedPenultimateMutation);
1277
1278 auto generatedMutation = ShadowViewMutation::UpdateMutation(
1279 mutatedShadowView, keyframe.viewEnd, keyframe.parentView);
1280 ABI49_0_0React_native_assert(generatedMutation.oldChildShadowView.tag > 0);
1281 ABI49_0_0React_native_assert(generatedMutation.newChildShadowView.tag > 0);
1282 PrintMutationInstruction(
1283 "Queueing up final mutation instruction - synthetic",
1284 generatedMutation);
1285 mutationsList.push_back(generatedMutation);
1286 } else {
1287 auto mutation = ShadowViewMutation::UpdateMutation(
1288 keyframe.viewPrev, keyframe.viewEnd, keyframe.parentView);
1289 PrintMutationInstruction(
1290 logPrefix +
1291 "Animation Complete: Queuing up Final Synthetic Mutation:",
1292 mutation);
1293 ABI49_0_0React_native_assert(mutation.oldChildShadowView.tag > 0);
1294 ABI49_0_0React_native_assert(mutation.newChildShadowView.tag > 0);
1295 mutationsList.push_back(std::move(mutation));
1296 }
1297 }
1298 }
1299
1300 #pragma mark - Private
1301
1302 void LayoutAnimationKeyFrameManager::
adjustImmediateMutationIndicesForDelayedMutations(SurfaceId surfaceId,ShadowViewMutation & mutation,bool skipLastAnimation,bool lastAnimationOnly) const1303 adjustImmediateMutationIndicesForDelayedMutations(
1304 SurfaceId surfaceId,
1305 ShadowViewMutation &mutation,
1306 bool skipLastAnimation,
1307 bool lastAnimationOnly) const {
1308 bool isRemoveMutation = mutation.type == ShadowViewMutation::Type::Remove;
1309 ABI49_0_0React_native_assert(
1310 isRemoveMutation || mutation.type == ShadowViewMutation::Type::Insert);
1311
1312 // TODO: turn all of this into a lambda and share code?
1313 if (mutation.mutatedViewIsVirtual()) {
1314 PrintMutationInstruction(
1315 "[IndexAdjustment] Not calling adjustImmediateMutationIndicesForDelayedMutations, is virtual, for:",
1316 mutation);
1317 return;
1318 }
1319
1320 PrintMutationInstruction(
1321 "[IndexAdjustment] Calling adjustImmediateMutationIndicesForDelayedMutations for:",
1322 mutation);
1323
1324 // First, collect all final mutations that could impact this immediate
1325 // mutation.
1326 std::vector<ShadowViewMutation const *> candidateMutations{};
1327
1328 for (auto inflightAnimationIt =
1329 inflightAnimations_.rbegin() + (skipLastAnimation ? 1 : 0);
1330 inflightAnimationIt != inflightAnimations_.rend();
1331 inflightAnimationIt++) {
1332 auto &inflightAnimation = *inflightAnimationIt;
1333 if (inflightAnimation.surfaceId != surfaceId) {
1334 continue;
1335 }
1336 if (inflightAnimation.completed) {
1337 continue;
1338 }
1339
1340 for (auto const &animatedKeyFrame : inflightAnimation.keyFrames) {
1341 if (animatedKeyFrame.invalidated) {
1342 continue;
1343 }
1344
1345 // Detect if they're in the same view hierarchy, but not equivalent
1346 // We've already detected direct conflicts and removed them.
1347 if (animatedKeyFrame.parentView.tag != mutation.parentShadowView.tag) {
1348 continue;
1349 }
1350
1351 for (auto const &delayedMutation :
1352 animatedKeyFrame.finalMutationsForKeyFrame) {
1353 if (delayedMutation.type != ShadowViewMutation::Type::Remove) {
1354 continue;
1355 }
1356 if (delayedMutation.mutatedViewIsVirtual()) {
1357 continue;
1358 }
1359 if (delayedMutation.oldChildShadowView.tag ==
1360 (isRemoveMutation ? mutation.oldChildShadowView.tag
1361 : mutation.newChildShadowView.tag)) {
1362 continue;
1363 }
1364
1365 PrintMutationInstructionRelative(
1366 "[IndexAdjustment] adjustImmediateMutationIndicesForDelayedMutations CANDIDATE for:",
1367 mutation,
1368 delayedMutation);
1369 candidateMutations.push_back(&delayedMutation);
1370 }
1371 }
1372
1373 if (lastAnimationOnly) {
1374 break;
1375 }
1376 }
1377
1378 // While the mutation keeps being affected, keep checking. We use the vector
1379 // so we only perform one adjustment per delayed mutation. See comments at
1380 // bottom of adjustDelayedMutationIndicesForMutation for further explanation.
1381 bool changed = true;
1382 int adjustedDelta = 0;
1383 while (changed) {
1384 changed = false;
1385 candidateMutations.erase(
1386 std::remove_if(
1387 candidateMutations.begin(),
1388 candidateMutations.end(),
1389 [&changed, &mutation, &adjustedDelta, &isRemoveMutation](
1390 ShadowViewMutation const *candidateMutation) {
1391 bool indexConflicts =
1392 (candidateMutation->index < mutation.index ||
1393 (isRemoveMutation &&
1394 candidateMutation->index == mutation.index));
1395 if (indexConflicts) {
1396 mutation.index++;
1397 adjustedDelta++;
1398 changed = true;
1399 PrintMutationInstructionRelative(
1400 "[IndexAdjustment] adjustImmediateMutationIndicesForDelayedMutations: Adjusting mutation UPWARD",
1401 mutation,
1402 *candidateMutation);
1403 return true;
1404 }
1405 return false;
1406 }),
1407 candidateMutations.end());
1408 }
1409 }
1410
adjustDelayedMutationIndicesForMutation(SurfaceId surfaceId,ShadowViewMutation const & mutation,bool skipLastAnimation) const1411 void LayoutAnimationKeyFrameManager::adjustDelayedMutationIndicesForMutation(
1412 SurfaceId surfaceId,
1413 ShadowViewMutation const &mutation,
1414 bool skipLastAnimation) const {
1415 bool isRemoveMutation = mutation.type == ShadowViewMutation::Type::Remove;
1416 bool isInsertMutation = mutation.type == ShadowViewMutation::Type::Insert;
1417 auto tag = isRemoveMutation ? mutation.oldChildShadowView.tag
1418 : mutation.newChildShadowView.tag;
1419 ABI49_0_0React_native_assert(isRemoveMutation || isInsertMutation);
1420
1421 if (mutation.mutatedViewIsVirtual()) {
1422 PrintMutationInstruction(
1423 "[IndexAdjustment] Not calling adjustDelayedMutationIndicesForMutation, is virtual, for:",
1424 mutation);
1425 return;
1426 }
1427
1428 // First, collect all final mutations that could impact this immediate
1429 // mutation.
1430 std::vector<ShadowViewMutation *> candidateMutations{};
1431
1432 for (auto inflightAnimationIt =
1433 inflightAnimations_.rbegin() + (skipLastAnimation ? 1 : 0);
1434 inflightAnimationIt != inflightAnimations_.rend();
1435 inflightAnimationIt++) {
1436 auto &inflightAnimation = *inflightAnimationIt;
1437
1438 if (inflightAnimation.surfaceId != surfaceId) {
1439 continue;
1440 }
1441 if (inflightAnimation.completed) {
1442 continue;
1443 }
1444
1445 for (auto &animatedKeyFrame : inflightAnimation.keyFrames) {
1446 if (animatedKeyFrame.invalidated) {
1447 continue;
1448 }
1449
1450 // Detect if they're in the same view hierarchy, but not equivalent
1451 // (We've already detected direct conflicts and handled them above)
1452 if (animatedKeyFrame.parentView.tag != mutation.parentShadowView.tag) {
1453 continue;
1454 }
1455
1456 for (auto &finalAnimationMutation :
1457 animatedKeyFrame.finalMutationsForKeyFrame) {
1458 if (finalAnimationMutation.oldChildShadowView.tag == tag) {
1459 continue;
1460 }
1461
1462 if (finalAnimationMutation.type != ShadowViewMutation::Type::Remove) {
1463 continue;
1464 }
1465 if (finalAnimationMutation.mutatedViewIsVirtual()) {
1466 continue;
1467 }
1468
1469 PrintMutationInstructionRelative(
1470 "[IndexAdjustment] adjustDelayedMutationIndicesForMutation: CANDIDATE:",
1471 mutation,
1472 finalAnimationMutation);
1473 candidateMutations.push_back(&finalAnimationMutation);
1474 }
1475 }
1476 }
1477
1478 // Because the finalAnimations are not sorted in any way, it is possible to
1479 // have some sequence like:
1480 // * DELAYED REMOVE 10 from {TAG}
1481 // * DELAYED REMOVE 9 from {TAG}
1482 // * ...
1483 // * DELAYED REMOVE 5 from {TAG}
1484 // with mutation: INSERT 6/REMOVE 6. This would cause the first few mutations
1485 // to *not* be adjusted, even though they would be impacted by mutation or
1486 // vice-versa after later adjustments are applied. Therefore, we just keep
1487 // recursing while there are any changes. This isn't great, but is good enough
1488 // for now until we change these data-structures.
1489 bool changed = true;
1490 while (changed) {
1491 changed = false;
1492 candidateMutations.erase(
1493 std::remove_if(
1494 candidateMutations.begin(),
1495 candidateMutations.end(),
1496 [&mutation, &isRemoveMutation, &isInsertMutation, &changed](
1497 ShadowViewMutation *candidateMutation) {
1498 if (isRemoveMutation &&
1499 mutation.index <= candidateMutation->index) {
1500 candidateMutation->index--;
1501 changed = true;
1502 PrintMutationInstructionRelative(
1503 "[IndexAdjustment] adjustDelayedMutationIndicesForMutation: Adjusting mutation DOWNWARD",
1504 mutation,
1505 *candidateMutation);
1506 return true;
1507 } else if (
1508 isInsertMutation &&
1509 mutation.index <= candidateMutation->index) {
1510 candidateMutation->index++;
1511 changed = true;
1512 PrintMutationInstructionRelative(
1513 "[IndexAdjustment] adjustDelayedMutationIndicesForMutation: Adjusting mutation UPWARD",
1514 mutation,
1515 *candidateMutation);
1516 return true;
1517 }
1518 return false;
1519 }),
1520 candidateMutations.end());
1521 }
1522 }
1523
getAndEraseConflictingAnimations(SurfaceId surfaceId,ShadowViewMutationList const & mutations,std::vector<AnimationKeyFrame> & conflictingAnimations) const1524 void LayoutAnimationKeyFrameManager::getAndEraseConflictingAnimations(
1525 SurfaceId surfaceId,
1526 ShadowViewMutationList const &mutations,
1527 std::vector<AnimationKeyFrame> &conflictingAnimations) const {
1528 ShadowViewMutationList localConflictingMutations{};
1529 for (auto const &mutation : mutations) {
1530 if (mutation.type == ShadowViewMutation::Type::RemoveDeleteTree) {
1531 continue;
1532 }
1533
1534 bool mutationIsCreateOrDelete =
1535 mutation.type == ShadowViewMutation::Type::Create ||
1536 mutation.type == ShadowViewMutation::Type::Delete;
1537 auto const &baselineShadowView =
1538 (mutation.type == ShadowViewMutation::Type::Insert ||
1539 mutation.type == ShadowViewMutation::Type::Create)
1540 ? mutation.newChildShadowView
1541 : mutation.oldChildShadowView;
1542 auto baselineTag = baselineShadowView.tag;
1543
1544 for (auto &inflightAnimation : inflightAnimations_) {
1545 if (inflightAnimation.surfaceId != surfaceId) {
1546 continue;
1547 }
1548 if (inflightAnimation.completed) {
1549 continue;
1550 }
1551
1552 for (auto it = inflightAnimation.keyFrames.begin();
1553 it != inflightAnimation.keyFrames.end();) {
1554 auto &animatedKeyFrame = *it;
1555
1556 if (animatedKeyFrame.invalidated) {
1557 continue;
1558 }
1559
1560 // A conflict is when either: the animated node itself is mutated
1561 // directly; or, the parent of the node is created or deleted. In cases
1562 // of reparenting - say, the parent is deleted but the node was moved to
1563 // a different parent first - the reparenting (remove/insert) conflict
1564 // will be detected before we process the parent DELETE.
1565 // Parent deletion is important because deleting a parent recursively
1566 // deletes all children. If we previously deferred deletion of a child,
1567 // we need to force deletion/removal to happen immediately.
1568 bool conflicting = animatedKeyFrame.tag == baselineTag ||
1569 (mutationIsCreateOrDelete &&
1570 animatedKeyFrame.parentView.tag == baselineTag &&
1571 animatedKeyFrame.parentView.tag != 0);
1572
1573 // Conflicting animation detected: if we're mutating a tag under
1574 // animation, or deleting the parent of a tag under animation, or
1575 // reparenting.
1576 if (conflicting) {
1577 animatedKeyFrame.invalidated = true;
1578
1579 // We construct a list of all conflicting animations, whether or not
1580 // they have a "final mutation" to execute. This is important with,
1581 // for example, "insert" mutations where the final update needs to set
1582 // opacity to "1", even if there's no final ShadowNode update.
1583 // TODO: don't animate virtual views in the first place?
1584 bool isVirtual = false;
1585 for (const auto &finalMutationForKeyFrame :
1586 animatedKeyFrame.finalMutationsForKeyFrame) {
1587 isVirtual =
1588 isVirtual || finalMutationForKeyFrame.mutatedViewIsVirtual();
1589
1590 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
1591 PrintMutationInstructionRelative(
1592 "Found mutation that conflicts with existing in-flight animation:",
1593 mutation,
1594 finalMutationForKeyFrame);
1595 #endif
1596 }
1597
1598 conflictingAnimations.push_back(animatedKeyFrame);
1599 for (const auto &finalMutationForKeyFrame :
1600 animatedKeyFrame.finalMutationsForKeyFrame) {
1601 if (!isVirtual ||
1602 finalMutationForKeyFrame.type ==
1603 ShadowViewMutation::Type::Delete) {
1604 localConflictingMutations.push_back(finalMutationForKeyFrame);
1605 }
1606 }
1607
1608 // Delete from existing animation
1609 it = inflightAnimation.keyFrames.erase(it);
1610 } else {
1611 it++;
1612 }
1613 }
1614 }
1615 }
1616
1617 // Recurse, in case conflicting mutations conflict with other existing
1618 // animations
1619 if (!localConflictingMutations.empty()) {
1620 getAndEraseConflictingAnimations(
1621 surfaceId, localConflictingMutations, conflictingAnimations);
1622 }
1623 }
1624
deleteAnimationsForStoppedSurfaces() const1625 void LayoutAnimationKeyFrameManager::deleteAnimationsForStoppedSurfaces()
1626 const {
1627 bool inflightAnimationsExistInitially = !inflightAnimations_.empty();
1628
1629 // Execute stopSurface on any ongoing animations
1630 if (inflightAnimationsExistInitially) {
1631 butter::set<SurfaceId> surfaceIdsToStop{};
1632 {
1633 std::lock_guard<std::mutex> lock(surfaceIdsToStopMutex_);
1634 surfaceIdsToStop = surfaceIdsToStop_;
1635 surfaceIdsToStop_.clear();
1636 }
1637
1638 #ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
1639 std::ostringstream surfaceIdsStr;
1640 std::copy(
1641 surfaceIdsToStop.begin(),
1642 surfaceIdsToStop.end(),
1643 std::ostream_iterator<SurfaceId>(surfaceIdsStr, ", "));
1644 LOG(ERROR) << "LayoutAnimations: stopping animations due to stopSurface on "
1645 << surfaceIdsStr.str();
1646 #endif
1647
1648 for (auto it = inflightAnimations_.begin();
1649 it != inflightAnimations_.end();) {
1650 const auto &animation = *it;
1651 if (surfaceIdsToStop.find(animation.surfaceId) !=
1652 surfaceIdsToStop.end()) {
1653 it = inflightAnimations_.erase(it);
1654 } else {
1655 it++;
1656 }
1657 }
1658 }
1659 }
1660
1661 } // namespace ABI49_0_0facebook::ABI49_0_0React
1662