From 94969edf7c317c6e2b15ae6a6e477efb09e1fede Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Sat, 20 Apr 2019 10:49:53 -0700 Subject: [PATCH] Fabric: Say hello to `MountingTransactionSynchronizer` Summary: ShadowTree commits happen concurrently with limited synchronization that only ensures the correctness of the commit from ShadowTree perspective. At the same time artifacts of the commit () needs to be delivered (also concurrently) to the proper thread and executed in order (not-concurrently). To achieve this we need some synchronization mechanism on the receiving (mounting) side. This class implements this process. Practically, this diff fixes a problem with glitching UI during the very first render of Fabric screen. Reviewed By: JoshuaGross Differential Revision: D15021794 fbshipit-source-id: 62982425300c515e92b91e1e660b45455a5446e9 --- React/Fabric/Mounting/RCTMountingManager.mm | 40 +++++++++--- .../MountingTransactionSynchronizer.cpp | 61 +++++++++++++++++++ .../MountingTransactionSynchronizer.h | 45 ++++++++++++++ 3 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 ReactCommon/fabric/mounting/MountingTransactionSynchronizer.cpp create mode 100644 ReactCommon/fabric/mounting/MountingTransactionSynchronizer.h diff --git a/React/Fabric/Mounting/RCTMountingManager.mm b/React/Fabric/Mounting/RCTMountingManager.mm index 333e2c55680..b34ba11773d 100644 --- a/React/Fabric/Mounting/RCTMountingManager.mm +++ b/React/Fabric/Mounting/RCTMountingManager.mm @@ -7,17 +7,21 @@ #import "RCTMountingManager.h" +#import + #import #import #import #import #import #import +#import #import "RCTComponentViewProtocol.h" #import "RCTComponentViewRegistry.h" #import "RCTConversions.h" +using namespace facebook; using namespace facebook::react; // `Create` instruction @@ -200,7 +204,9 @@ static void RNPerformMountInstructions(ShadowViewMutationList const &mutations, } } -@implementation RCTMountingManager +@implementation RCTMountingManager { + better::map syncronizers_; +} - (instancetype)init { @@ -211,35 +217,49 @@ static void RNPerformMountInstructions(ShadowViewMutationList const &mutations, return self; } -- (void)scheduleTransaction:(facebook::react::MountingTransaction &&)mountingTransaction; +- (void)scheduleTransaction:(MountingTransaction &&)mountingTransaction; { if (RCTIsMainQueue()) { // Already on the proper thread, so: // * No need to do a thread jump; // * No need to do expensive copy of all mutations; // * No need to allocate a block. - [self mountMutations:mountingTransaction.getMutations() rootTag:mountingTransaction.getSurfaceId()]; + [self mountMutations:std::move(mountingTransaction)]; return; } // We need a non-reference for `mountingTransaction` to allow copy semantic. - __block auto mountingTransactionCopy = MountingTransaction{mountingTransaction}; + auto sharedMountingTransaction = std::make_shared(std::move(mountingTransaction)); RCTExecuteOnMainQueue(^{ RCTAssertMainQueue(); - [self mountMutations:mountingTransactionCopy.getMutations() rootTag:mountingTransactionCopy.getSurfaceId()]; + [self mountMutations:std::move(*sharedMountingTransaction)]; }); } -- (void)mountMutations:(ShadowViewMutationList const &)mutations rootTag:(ReactTag)rootTag +- (void)mountMutations:(MountingTransaction &&)mountingTransaction { - SystraceSection s("-[RCTMountingManager mountMutations:rootTag:]"); + SystraceSection s("-[RCTMountingManager mountMutations:]"); RCTAssertMainQueue(); - [self.delegate mountingManager:self willMountComponentsWithRootTag:rootTag]; - RNPerformMountInstructions(mutations, self.componentViewRegistry); - [self.delegate mountingManager:self didMountComponentsWithRootTag:rootTag]; + auto &syncronizer = syncronizers_[mountingTransaction.getSurfaceId()]; + + syncronizer.push(std::move(mountingTransaction)); + + while (true) { + auto mountingTransactionOptional = syncronizer.pull(); + if (!mountingTransactionOptional.has_value()) { + break; + } + + auto transaction = std::move(*mountingTransactionOptional); + auto surfaceId = transaction.getSurfaceId(); + + [self.delegate mountingManager:self willMountComponentsWithRootTag:surfaceId]; + RNPerformMountInstructions(transaction.getMutations(), self.componentViewRegistry); + [self.delegate mountingManager:self didMountComponentsWithRootTag:surfaceId]; + } } - (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag diff --git a/ReactCommon/fabric/mounting/MountingTransactionSynchronizer.cpp b/ReactCommon/fabric/mounting/MountingTransactionSynchronizer.cpp new file mode 100644 index 00000000000..23ecd290306 --- /dev/null +++ b/ReactCommon/fabric/mounting/MountingTransactionSynchronizer.cpp @@ -0,0 +1,61 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "MountingTransactionSynchronizer.h" + +namespace facebook { +namespace react { + +void MountingTransactionSynchronizer::push(MountingTransaction &&transaction) { + assert(transaction.getRevision() < 1 && "Invalid transaction revision."); + + if (transaction.getRevision() == 1 && revision_ > 0) { + // Special case: + // Seems we have a completely new flow of mutations probably caused by the + // hot-reload process. At this point, there is no way to guarantee anything, + // so let's just start over. + queue_.clear(); + revision_ = 0; + } + + auto it = queue_.begin(); + + while (it != queue_.end()) { + assert( + it->getRevision() != transaction.getRevision() && + "Attempt to re-insert transaction with same revision."); + + if (it->getRevision() > transaction.getRevision()) { + queue_.insert(it, std::move(transaction)); + return; + } + + it++; + } + + queue_.push_back(std::move(transaction)); +} + +better::optional MountingTransactionSynchronizer::pull() { + if (queue_.size() == 0) { + return {}; + } + + if (queue_.front().getRevision() != revision_ + 1) { + return {}; + } + + revision_++; + + auto transaction = std::move(queue_.front()); + queue_.pop_front(); + + return {std::move(transaction)}; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/mounting/MountingTransactionSynchronizer.h b/ReactCommon/fabric/mounting/MountingTransactionSynchronizer.h new file mode 100644 index 00000000000..5e4989a1ee5 --- /dev/null +++ b/ReactCommon/fabric/mounting/MountingTransactionSynchronizer.h @@ -0,0 +1,45 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include + +namespace facebook { +namespace react { + +/* + * ShadowTree commits happen concurrently with limited synchronization that only + * ensures the correctness of the commit from ShadowTree perspective. At the + * same time artifacts of the commit () needs to be delivered (also + * concurrently) to the proper thread and executed in order (not-concurrently). + * To achieve this we need some synchronization mechanism on the receiving + * (mounting) side. This class implements this process. This class is *not* + * thread-safe (and must not be). + */ +class MountingTransactionSynchronizer final { + public: + /* + * Pushes (adds) a new MountingTransaction to the internal queue. + */ + void push(MountingTransaction &&transaction); + + /* + * Pulls (returns and removes) a MountingTransaction from the internal queue + * if it has something ready to be pulled. Return an empty optional otherwise. + */ + better::optional pull(); + + private: + MountingTransaction::Revision revision_{0}; + std::deque queue_{}; +}; + +} // namespace react +} // namespace facebook