Files
react-native/ReactCommon/react/renderer/core/ConcreteState.h
T
Valentin Shergin a8b090b128 Fabric: Another attempt to deal with failing state updates
Summary:
This is *another* attempt to solve a failed state update problem.

Unfortunately, some applications are inherently not compatible with the "let's recommit the update on the application side in case it failed" approach. The problem is that if we call `updateState` on the application side, we miss the original event window. E.g. if we need to deliver some state update with AsyncBatched priority and if the update fails, we lose the opportunity to commit it on time. These issues can be critical for some complex use-cases as ComponentKit interop.

This diff adds implementation for `updateState` that does the work a bit differently. For all failed state updates it tres to recommit them asap using `ShadowTree::commit` and calling lambda on every attempt. With this approach the update might fail in two cases:
The node disappeared from the tree, so there is no way to update it.
The lambda returned `nullptr` indicating that the update is no longer needed.

We need this for the ComponentKit interoperability layer that is very sensitive for missing state updates.

Changelog: [Internal] Fabric-specific internal change.

Reviewed By: sammy-SC

Differential Revision: D23603958

fbshipit-source-id: a3b8c09fb2f1c8302583aa5880b48fc0840224e3
2020-09-11 09:22:08 -07:00

149 lines
4.3 KiB
C++

/*
* 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 <functional>
#include <memory>
#include <react/renderer/core/State.h>
namespace facebook {
namespace react {
/*
* Concrete and only template implementation of State interface.
* State wraps an arbitrary data type and provides an interface to initiate a
* state update transaction. A data object does not need to be copyable but
* needs to be moveable.
*/
template <typename DataT>
class ConcreteState : public State {
public:
using Shared = std::shared_ptr<ConcreteState const>;
using Data = DataT;
using SharedData = std::shared_ptr<Data const>;
/*
* Creates an updated `State` object with given previous one and `data`.
*/
explicit ConcreteState(SharedData const &data, State const &state)
: State(data, state) {}
/*
* Creates a first-of-its-family `State` object with given `family` and
* `data`.
*/
explicit ConcreteState(
SharedData const &data,
ShadowNodeFamily::Shared const &family)
: State(data, family) {}
virtual ~ConcreteState() = default;
/*
* Returns stored data.
*/
Data const &getData() const {
return *std::static_pointer_cast<Data const>(data_);
}
/*
* 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,
std::function<void()> failureCallback = nullptr,
EventPriority priority = EventPriority::AsynchronousUnbatched) const {
updateState(
[data = std::move(newData)](Data const &oldData) mutable -> Data && {
return std::move(data);
},
failureCallback,
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<Data(Data const &oldData)> callback,
std::function<void()> failureCallback = nullptr,
EventPriority priority = EventPriority::AsynchronousBatched) const {
auto family = family_.lock();
if (!family) {
// No more nodes of this family exist anymore,
// updating state is impossible.
return;
}
auto stateUpdate = StateUpdate{
family,
[=](StateData::Shared const &oldData) -> StateData::Shared {
assert(oldData);
return std::make_shared<Data const>(
callback(*std::static_pointer_cast<Data const>(oldData)));
},
failureCallback,
false};
family->dispatchRawState(std::move(stateUpdate), priority);
}
/*
* An experimental version of `updateState` function that re-commit the state
* update over and over again until it succeeded. To cancel the state update
* operation, the state update lambda needs to return `nullptr`.
*/
void updateStateWithAutorepeat(
std::function<StateData::Shared(Data const &oldData)> callback,
EventPriority priority = EventPriority::AsynchronousBatched) const {
auto family = family_.lock();
if (!family) {
// No more nodes of this family exist anymore,
// updating state is impossible.
return;
}
auto stateUpdate = StateUpdate{
family,
[=](StateData::Shared const &oldData) -> StateData::Shared {
assert(oldData);
return callback(*std::static_pointer_cast<Data const>(oldData));
},
nullptr,
true,
};
family->dispatchRawState(std::move(stateUpdate), priority);
}
#ifdef ANDROID
folly::dynamic getDynamic() const override {
return getData().getDynamic();
}
void updateState(folly::dynamic data, std::function<void()> failureCallback)
const override {
updateState(std::move(Data(getData(), data)), failureCallback);
}
#endif
};
} // namespace react
} // namespace facebook