From 2bc8ce15494c5ea4304d6ce5cb0e081f41519fa6 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Mon, 24 Aug 2020 06:39:29 -0700 Subject: [PATCH] Introduce ConcreteStateTeller Summary: Changelog: [internal] # What is Teller? Teller is a bank's employee who deals with the customer on behalf of the bank. In Fabric's scenario it is a class that on behalf of the view deals with State. # Why do we need it? Dealing with `ConcreteState` can be complicated and patterns are often repeated among different component views. `ConcreteStateTeller` aims to resolve these issues. Examples: - You can call teller's methods without checking for nullptr (we have had crashes because of this before). - Methods are save to be called on any thread. - Mechanism to retry state update if it fails is built in. It is designed to be used from ComponentView so views don't have to talk directly to `ConcreteState`. Reviewed By: JoshuaGross, shergin Differential Revision: D23216865 fbshipit-source-id: 90a50702e036eac084f89743ebab687a67182dc0 --- .../react/renderer/core/ConcreteShadowNode.h | 2 + .../react/renderer/core/ConcreteStateTeller.h | 152 ++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 ReactCommon/react/renderer/core/ConcreteStateTeller.h diff --git a/ReactCommon/react/renderer/core/ConcreteShadowNode.h b/ReactCommon/react/renderer/core/ConcreteShadowNode.h index 754194b01b9..e2c601a8858 100644 --- a/ReactCommon/react/renderer/core/ConcreteShadowNode.h +++ b/ReactCommon/react/renderer/core/ConcreteShadowNode.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -48,6 +49,7 @@ class ConcreteShadowNode : public BaseShadowNodeT { using SharedConcreteEventEmitter = std::shared_ptr; using SharedConcreteShadowNode = std::shared_ptr; using ConcreteState = ConcreteState; + using ConcreteStateTeller = ConcreteStateTeller; using ConcreteStateData = StateDataT; static ComponentName Name() { diff --git a/ReactCommon/react/renderer/core/ConcreteStateTeller.h b/ReactCommon/react/renderer/core/ConcreteStateTeller.h new file mode 100644 index 00000000000..2354ae0daaf --- /dev/null +++ b/ReactCommon/react/renderer/core/ConcreteStateTeller.h @@ -0,0 +1,152 @@ +/* + * 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 +#include + +namespace facebook { +namespace react { + +/* + * Wrapper for `ConreteState` class designed to make interactions with + * ConcreteState easier. + */ +template +class ConcreteStateTeller { + public: + using Data = typename ConcreteStateT::Data; + + /* + * Sets backing `ConcreteState` on which all the methods will be called. + * Can be called from any thread. + */ + void setConcreteState(State::Shared const &state) { + std::lock_guard lock(mutex_); + concreteState_ = std::static_pointer_cast(state); + } + + /* + * Removes reference to `ConcreteState` previously set in `setConcreteState`. + * Can be called from any thread. + */ + void invalidate() { + std::lock_guard lock(mutex_); + concreteState_ = nullptr; + } + + /* + * Returns data if state isn't nullptr. + * Can be called from any thread. + */ + better::optional getData() const { + std::lock_guard lock(mutex_); + if (concreteState_) { + return concreteState_->getData(); + } else { + return {}; + } + } + + /* + * Returns true if backing state isn't nullptr, false otherwise. + * Can be called from any thread. + */ + bool isValid() const { + std::lock_guard lock(mutex_); + return concreteState_ != nullptr; + } + + /* + * Initiate a state update process with given new data and priority. + * This is a simplified convenience version of the method that receives a + * function for cases where a new value of data does not depend on an old + * value. + */ + void updateState( + Data &&newData, + EventPriority priority = EventPriority::AsynchronousUnbatched) const { + updateState( + [data = std::move(newData)](Data const &oldData) -> Data { + return std::move(data); + }, + priority); + } + + /* + * Initiate a state update process with a given function (that transforms an + * old data value to a new one) and priority. The update function can be + * called from any thread any moment later. The function can be called only + * once or not called at all (in the case where the node was already unmounted + * and updating makes no sense). The state update operation might fail in case + * of conflict. + */ + void updateState( + std::function callback, + EventPriority priority = EventPriority::AsynchronousBatched) const { + std::shared_ptr concreteState; + { + std::lock_guard lock(mutex_); + if (!concreteState_) { + return; + } + concreteState = concreteState_; + } + + concreteState->updateState( + callback, + [=]() { + updateStateRetryIfNecesarry_(concreteState, callback, priority, 1); + }, + priority); + } + + private: + /* + * Protected by `mutex_`. + */ + std::shared_ptr concreteState_; + + /* + * Protects `concreteState_`. + */ + std::mutex mutable mutex_; + + void updateStateRetryIfNecesarry_( + std::shared_ptr concreteState, + std::function callback, + EventPriority priority, + int retryCount) const { + { + std::lock_guard lock(mutex_); + + if (concreteState != concreteState_) { + LOG(WARNING) << "ConcreteState_ changed while retrying"; + return; + } + } + + if (retryCount > 60) { + LOG(ERROR) << "Exceeded 60 retries"; + assert(false); + return; + } + + concreteState->updateState( + callback, + [=] { + updateStateRetryIfNecesarry_( + concreteState, callback, priority, retryCount + 1); + }, + priority); + } +}; + +} // namespace react +} // namespace facebook