diff --git a/React/Fabric/Mounting/RCTMountingManager.mm b/React/Fabric/Mounting/RCTMountingManager.mm index 333e2c556..b34ba1177 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 000000000..23ecd2903 --- /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 000000000..5e4989a1e --- /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