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
This commit is contained in:
Valentin Shergin
2019-04-20 10:49:53 -07:00
committed by Facebook Github Bot
parent af0daaf583
commit 94969edf7c
3 changed files with 136 additions and 10 deletions

View File

@@ -7,17 +7,21 @@
#import "RCTMountingManager.h"
#import <better/map.h>
#import <React/RCTAssert.h>
#import <React/RCTFollyConvert.h>
#import <React/RCTUtils.h>
#import <react/core/LayoutableShadowNode.h>
#import <react/core/RawProps.h>
#import <react/debug/SystraceSection.h>
#import <react/mounting/MountingTransactionSynchronizer.h>
#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<SurfaceId, MountingTransactionSynchronizer> 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<MountingTransaction>(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

View File

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

View File

@@ -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 <better/optional.h>
#include <react/mounting/MountingTransaction.h>
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<MountingTransaction> pull();
private:
MountingTransaction::Revision revision_{0};
std::deque<MountingTransaction> queue_{};
};
} // namespace react
} // namespace facebook