#include namespace reanimated::css { bool CSSAnimationsRegistry::isEmpty() const { // The registry is empty if has no registered animations and no updates // stored in the updates registry return UpdatesRegistry::isEmpty() && registry_.empty(); } bool CSSAnimationsRegistry::hasUpdates() const { return !runningAnimationIndicesMap_.empty() || !delayedAnimationsManager_.empty() || !animationsToRevertMap_.empty(); } void CSSAnimationsRegistry::apply( jsi::Runtime &rt, const std::shared_ptr &shadowNode, const std::optional> &animationNames, const CSSAnimationsMap &newAnimations, const CSSAnimationSettingsUpdatesMap &settingsUpdates, double timestamp) { const auto animationsVector = buildAnimationsVector(rt, shadowNode, animationNames, newAnimations); const auto viewTag = shadowNode->getTag(); if (animationsVector.empty()) { remove(viewTag); return; } registry_.erase(viewTag); registry_.emplace( viewTag, RegistryEntry{ std::move(animationsVector), buildAnimationToIndexMap(animationsVector)}); runningAnimationIndicesMap_[viewTag].clear(); std::vector updatedIndices; for (const auto &[index, _] : newAnimations) { updatedIndices.push_back(index); } for (const auto &[index, _] : settingsUpdates) { updatedIndices.push_back(index); } updateAnimationSettings(animationsVector, settingsUpdates, timestamp); for (size_t i = 0; i < animationsVector.size(); ++i) { scheduleOrActivateAnimation(i, animationsVector[i], timestamp); } updateViewAnimations(viewTag, updatedIndices, timestamp, false); applyViewAnimationsStyle(viewTag, timestamp); } void CSSAnimationsRegistry::remove(const Tag viewTag) { removeViewAnimations(viewTag); removeFromUpdatesRegistry(viewTag); registry_.erase(viewTag); } void CSSAnimationsRegistry::update(const double timestamp) { // Activate all delayed animations that should start now activateDelayedAnimations(timestamp); // Update styles in the registry for views which animations were reverted handleAnimationsToRevert(timestamp); // Iterate over active animations and update them for (auto it = runningAnimationIndicesMap_.begin(); it != runningAnimationIndicesMap_.end();) { const auto viewTag = it->first; const std::vector animationIndices = { it->second.begin(), it->second.end()}; updateViewAnimations(viewTag, animationIndices, timestamp, true); if (runningAnimationIndicesMap_[viewTag].empty()) { it = runningAnimationIndicesMap_.erase(it); } else { ++it; } } } CSSAnimationsVector CSSAnimationsRegistry::buildAnimationsVector( jsi::Runtime &rt, const std::shared_ptr &shadowNode, const std::optional> &animationNames, const std::optional &newAnimations) const { const auto registryIt = registry_.find(shadowNode->getTag()); // If animationNames has no value, that means no animations were added, // removed or reordered, so we can return the current animations vector from // the registry if (!animationNames.has_value()) { if (registryIt != registry_.end()) { return std::move(registryIt->second.animationsVector); } } CSSAnimationsVector animationsVector; const auto &animationNamesVector = animationNames.value(); const auto animationNamesSize = animationNamesVector.size(); animationsVector.reserve(animationNamesSize); std::unordered_map oldAnimationsMap; CSSAnimationsMap emptyAnimationsMap; const auto &newAnimationsMap = newAnimations.value_or(emptyAnimationsMap); if (registryIt != registry_.end()) { const auto &oldAnimations = registryIt->second.animationsVector; // Fill the map while maintaining reverse order (for quick pop from the end) for (auto it = oldAnimations.rbegin(); it != oldAnimations.rend(); ++it) { oldAnimationsMap[(*it)->getName()].emplace_back(*it); } } for (size_t i = 0; i < animationNamesSize; ++i) { const auto &newAnimationIt = newAnimationsMap.find(i); if (newAnimationIt != newAnimationsMap.end()) { animationsVector.emplace_back(newAnimationIt->second); continue; } const auto &name = animationNamesVector[i]; const auto &oldAnimationIt = oldAnimationsMap.find(name); if (oldAnimationIt == oldAnimationsMap.end()) { throw std::runtime_error( "[Reanimated] There is no animation with name " + name + " available to use at index " + std::to_string(i)); } animationsVector.emplace_back(oldAnimationIt->second.back()); oldAnimationIt->second.pop_back(); if (oldAnimationIt->second.empty()) { oldAnimationsMap.erase(oldAnimationIt); } } return animationsVector; } CSSAnimationsRegistry::AnimationToIndexMap CSSAnimationsRegistry::buildAnimationToIndexMap( const CSSAnimationsVector &animationsVector) const { AnimationToIndexMap animationToIndexMap; for (size_t i = 0; i < animationsVector.size(); ++i) { animationToIndexMap[animationsVector[i]] = i; } return animationToIndexMap; } void CSSAnimationsRegistry::updateAnimationSettings( const CSSAnimationsVector &animationsVector, const CSSAnimationSettingsUpdatesMap &settingsUpdates, const double timestamp) { for (size_t i = 0; i < animationsVector.size(); ++i) { const auto &animation = animationsVector[i]; const auto it = settingsUpdates.find(i); if (it != settingsUpdates.end()) { animation->updateSettings(it->second, timestamp); } } } void CSSAnimationsRegistry::updateViewAnimations( const Tag viewTag, const std::vector &animationIndices, const double timestamp, const bool addToBatch) { folly::dynamic result = folly::dynamic::object; std::shared_ptr shadowNode = nullptr; bool hasUpdates = false; for (const auto animationIndex : animationIndices) { const auto &animation = registry_[viewTag].animationsVector[animationIndex]; if (!shadowNode) { shadowNode = animation->getShadowNode(); } if (animation->getState(timestamp) == AnimationProgressState::Pending) { animation->run(timestamp); } bool updatesAddedToBatch = false; const auto updates = animation->update(timestamp); const auto newState = animation->getState(timestamp); if (newState == AnimationProgressState::Finished) { // Revert changes applied during animation if there is no forwards fill // mode if (addToBatch && !animation->hasForwardsFillMode()) { // We also have to manually commit style values // reverting the changes applied by the animation. hasUpdates = addStyleUpdates(result, animation->getResetStyle(), false) || hasUpdates; updatesAddedToBatch = true; // We want to remove style changes applied by the animation that is // finished and has no forwards fill mode. We cannot simply remove // properties from the style in the registry as it may be overridden // by the next animation. Instead, we are creating the new style // object without reverted (finished without forwards fill mode) // animations. animationsToRevertMap_[viewTag].insert(animationIndex); } } if (addToBatch && !updatesAddedToBatch) { hasUpdates = addStyleUpdates(result, updates, true) || hasUpdates; } if (newState != AnimationProgressState::Running) { runningAnimationIndicesMap_[viewTag].erase(animationIndex); } } if (hasUpdates) { addUpdatesToBatch(shadowNode, result); } } void CSSAnimationsRegistry::scheduleOrActivateAnimation( const size_t animationIndex, const std::shared_ptr &animation, const double timestamp) { // Remove the animation from delayed (if it is already added to // delayed animations) delayedAnimationsManager_.remove(animation); const auto startTimestamp = animation->getStartTimestamp(timestamp); if (startTimestamp > timestamp) { // If the animation is delayed, schedule it for activation // (Only if it isn't paused) if (animation->getState(timestamp) != AnimationProgressState::Paused) { delayedAnimationsManager_.add(startTimestamp, animation); } } else { const auto viewTag = animation->getShadowNode()->getTag(); runningAnimationIndicesMap_[viewTag].insert(animationIndex); } } void CSSAnimationsRegistry::removeViewAnimations(const Tag viewTag) { const auto it = registry_.find(viewTag); if (it == registry_.end()) { return; } for (const auto &animation : it->second.animationsVector) { delayedAnimationsManager_.remove(animation); } runningAnimationIndicesMap_.erase(viewTag); } void CSSAnimationsRegistry::applyViewAnimationsStyle( const Tag viewTag, const double timestamp) { const auto it = registry_.find(viewTag); // Remove the style from the registry if there are no animations for the view if (it == registry_.end() || it->second.animationsVector.empty()) { removeFromUpdatesRegistry(viewTag); return; } folly::dynamic updatedStyle = folly::dynamic::object; std::shared_ptr shadowNode = nullptr; for (const auto &animation : it->second.animationsVector) { const auto startTimestamp = animation->getStartTimestamp(timestamp); folly::dynamic style; const auto ¤tState = animation->getState(timestamp); if (startTimestamp > timestamp && animation->hasBackwardsFillMode()) { style = animation->getBackwardsFillStyle(); } else if ( currentState == AnimationProgressState::Running || // Animation is paused after start (was running before) (currentState == AnimationProgressState::Paused && timestamp >= animation->getStartTimestamp(timestamp)) || // Animation is finished and has fill forwards fill mode (currentState == AnimationProgressState::Finished && animation->hasForwardsFillMode())) { style = animation->getCurrentInterpolationStyle(); } if (!shadowNode) { shadowNode = animation->getShadowNode(); } if (style.isObject()) { updatedStyle.update(style); } } setInUpdatesRegistry(shadowNode, updatedStyle); } void CSSAnimationsRegistry::activateDelayedAnimations(const double timestamp) { while (!delayedAnimationsManager_.empty() && delayedAnimationsManager_.top().timestamp <= timestamp) { const auto [_, animation] = delayedAnimationsManager_.pop(); const auto viewTag = animation->getShadowNode()->getTag(); // Add only these animations which weren't removed in the meantime if (registry_.find(viewTag) == registry_.end()) { continue; } const auto &animationToIndexMap = registry_[viewTag].animationToIndexMap; if (animationToIndexMap.find(animation) == animationToIndexMap.end()) { continue; } const auto animationIndex = animationToIndexMap.at(animation); runningAnimationIndicesMap_[viewTag].insert(animationIndex); } } void CSSAnimationsRegistry::handleAnimationsToRevert(const double timestamp) { for (const auto &[viewTag, _] : animationsToRevertMap_) { applyViewAnimationsStyle(viewTag, timestamp); } animationsToRevertMap_.clear(); } bool CSSAnimationsRegistry::addStyleUpdates( folly::dynamic &target, const folly::dynamic &updates, bool shouldOverride) { if (!updates.isObject()) { return false; } bool hasUpdates = false; for (const auto &[propertyName, propertyValue] : updates.items()) { if (shouldOverride || !target.count(propertyName) || target[propertyName].isNull()) { target[propertyName] = propertyValue; hasUpdates = true; } } return hasUpdates; } } // namespace reanimated::css