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
This commit is contained in:
Valentin Shergin 2019-01-16 20:17:01 -08:00 committed by Facebook Github Bot
parent cdb983d339
commit 7d630b92dc
4 changed files with 76 additions and 19 deletions

View File

@ -146,6 +146,10 @@ void ShadowNode::cloneChildrenIfShared() {
children_ = std::make_shared<SharedShadowNodeList>(*children_);
}
void ShadowNode::setMounted(bool mounted) const {
eventEmitter_->setEnabled(mounted);
}
bool ShadowNode::constructAncestorPath(
const ShadowNode &ancestorShadowNode,
std::vector<std::reference_wrapper<const ShadowNode>> &ancestors) const {

View File

@ -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.

View File

@ -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<std::mutex> 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<std::mutex> 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) {

View File

@ -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_;