From 7d630b92dc7a99717739141c16819f1e2eadb3e8 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Wed, 16 Jan 2019 20:17:01 -0800 Subject: [PATCH] Fabric: Lock-free events 5/n: New implementation of `toggleEventEmitters` (which does not rely on mutations) Summary: This diff implements a new algorithm that effectively marks `EventEmitter`s enabled or disabled. The previous implementation relied on a list of mutation instructions whereas the new one analyzes the shadow trees. The mutations-based approach didn't work well because mutations describe `ShadowView`s whereas some `ShadowNode`s are simply not views (like VirtualText), but we have to enable/disable them anyway. Reviewed By: sahrens Differential Revision: D13642594 fbshipit-source-id: 12169e11d5685e50bcd0d8c410498c594df744b4 --- .../fabric/core/shadownode/ShadowNode.cpp | 4 + .../fabric/core/shadownode/ShadowNode.h | 7 ++ ReactCommon/fabric/uimanager/ShadowTree.cpp | 83 +++++++++++++++---- ReactCommon/fabric/uimanager/ShadowTree.h | 1 - 4 files changed, 76 insertions(+), 19 deletions(-) diff --git a/ReactCommon/fabric/core/shadownode/ShadowNode.cpp b/ReactCommon/fabric/core/shadownode/ShadowNode.cpp index 8855179b2..07c5b9dd4 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNode.cpp +++ b/ReactCommon/fabric/core/shadownode/ShadowNode.cpp @@ -146,6 +146,10 @@ void ShadowNode::cloneChildrenIfShared() { children_ = std::make_shared(*children_); } +void ShadowNode::setMounted(bool mounted) const { + eventEmitter_->setEnabled(mounted); +} + bool ShadowNode::constructAncestorPath( const ShadowNode &ancestorShadowNode, std::vector> &ancestors) const { diff --git a/ReactCommon/fabric/core/shadownode/ShadowNode.h b/ReactCommon/fabric/core/shadownode/ShadowNode.h index 17dbb64bf..ea12b5f72 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNode.h +++ b/ReactCommon/fabric/core/shadownode/ShadowNode.h @@ -101,6 +101,13 @@ class ShadowNode : public virtual Sealable, */ void setLocalData(const SharedLocalData &localData); + /* + * Performs all side effects associated with mounting/unmounting in one place. + * This is not `virtual` on purpose, do not override this. + * `EventEmitter::DispatchMutex()` must be acquired before calling. + */ + void setMounted(bool mounted) const; + /* * Forms a list of all ancestors of the node relative to the given ancestor. * The list starts from the parent node and ends with the given ancestor node. diff --git a/ReactCommon/fabric/uimanager/ShadowTree.cpp b/ReactCommon/fabric/uimanager/ShadowTree.cpp index 08fd34dfe..ccb125ef4 100644 --- a/ReactCommon/fabric/uimanager/ShadowTree.cpp +++ b/ReactCommon/fabric/uimanager/ShadowTree.cpp @@ -16,6 +16,65 @@ namespace facebook { namespace react { +static void updateMountedFlag( + const SharedShadowNodeList &oldChildren, + const SharedShadowNodeList &newChildren) { + // This is a simplified version of Diffing algorithm that only updates + // `mounted` flag on `ShadowNode`s. The algorithm sets "mounted" flag before + // "unmounted" to allow `ShadowNode` detect a situation where the node was + // remounted. + + if (&oldChildren == &newChildren) { + // Lists are identical, nothing to do. + return; + } + + if (oldChildren.size() == 0 && newChildren.size() == 0) { + // Both lists are empty, nothing to do. + return; + } + + int index; + + // Stage 1: Mount and unmount "updated" children. + for (index = 0; index < oldChildren.size() && index < newChildren.size(); + index++) { + const auto &oldChild = oldChildren[index]; + const auto &newChild = newChildren[index]; + + if (oldChild == newChild) { + // Nodes are identical, skipping the subtree. + continue; + } + + if (oldChild->getTag() != newChild->getTag()) { + // Totally different nodes, updating is impossible. + break; + } + + newChild->setMounted(true); + oldChild->setMounted(false); + + updateMountedFlag(oldChild->getChildren(), newChild->getChildren()); + } + + int lastIndexAfterFirstStage = index; + + // State 2: Mount new children. + for (index = lastIndexAfterFirstStage; index < newChildren.size(); index++) { + const auto &newChild = newChildren[index]; + newChild->setMounted(true); + updateMountedFlag({}, newChild->getChildren()); + } + + // State 3: Unmount old children. + for (index = lastIndexAfterFirstStage; index < oldChildren.size(); index++) { + const auto &oldChild = oldChildren[index]; + oldChild->setMounted(false); + updateMountedFlag(oldChild->getChildren(), {}); + } +} + ShadowTree::ShadowTree( SurfaceId surfaceId, const LayoutConstraints &layoutConstraints, @@ -90,7 +149,12 @@ bool ShadowTree::commit( rootShadowNode_ = newRootShadowNode; - toggleEventEmitters(mutations); + { + std::lock_guard dispatchLock(EventEmitter::DispatchMutex()); + + updateMountedFlag( + oldRootShadowNode->getChildren(), newRootShadowNode->getChildren()); + } revision_++; @@ -153,23 +217,6 @@ void ShadowTree::emitLayoutEvents( } } -void ShadowTree::toggleEventEmitters( - const ShadowViewMutationList &mutations) const { - std::lock_guard lock(EventEmitter::DispatchMutex()); - - for (const auto &mutation : mutations) { - if (mutation.type == ShadowViewMutation::Create) { - mutation.newChildShadowView.eventEmitter->setEnabled(true); - } - } - - for (const auto &mutation : mutations) { - if (mutation.type == ShadowViewMutation::Delete) { - mutation.oldChildShadowView.eventEmitter->setEnabled(false); - } - } -} - #pragma mark - Delegate void ShadowTree::setDelegate(ShadowTreeDelegate const *delegate) { diff --git a/ReactCommon/fabric/uimanager/ShadowTree.h b/ReactCommon/fabric/uimanager/ShadowTree.h index 9bec4ae33..c0b8302f9 100644 --- a/ReactCommon/fabric/uimanager/ShadowTree.h +++ b/ReactCommon/fabric/uimanager/ShadowTree.h @@ -70,7 +70,6 @@ class ShadowTree final { const LayoutConstraints &layoutConstraints, const LayoutContext &layoutContext) const; - void toggleEventEmitters(const ShadowViewMutationList &mutations) const; void emitLayoutEvents(const ShadowViewMutationList &mutations) const; const SurfaceId surfaceId_;