Fabric: Introducting State, an escape path from unidirectional data flow

Summary:
In React Native there are several use cases where React State is not the only input that affects the component tree. E.g., in case of a <Modal> component, the screen size directly affects the layout of components inside modal; in the case of uncontrolled <TextInput> component, the text inside the input affects the layout of the surrounding components. `State` is a special (legit!) workaround for all those similar cases. Native part of React Native maintains a special shared object between all nodes of the same family and use that during cloning and commits.

See coming commits to know how to use that.

In the near future State will fully replace LocalData concept but for simplicity they both exist for now.

Reviewed By: mdvacca

Differential Revision: D14217184

fbshipit-source-id: 6e018c5b68208d662462013bce0f4e2733d2f673
This commit is contained in:
Valentin Shergin
2019-02-27 00:29:19 -08:00
committed by Facebook Github Bot
parent b9107084f7
commit 802534e611
38 changed files with 699 additions and 37 deletions

View File

@@ -8,6 +8,7 @@
#pragma once
#include <folly/SharedMutex.h>
#include <shared_mutex>
#include <mutex>
namespace facebook {

View File

@@ -22,7 +22,7 @@ class ImageComponentDescriptor final
: public ConcreteComponentDescriptor<ImageShadowNode> {
public:
ImageComponentDescriptor(
SharedEventDispatcher eventDispatcher,
EventDispatcher::Shared eventDispatcher,
const SharedContextContainer &contextContainer)
: ConcreteComponentDescriptor(eventDispatcher),
// TODO (39486757): implement image manager on Android, currently Android does

View File

@@ -21,7 +21,7 @@ class SliderComponentDescriptor final
: public ConcreteComponentDescriptor<SliderShadowNode> {
public:
SliderComponentDescriptor(
SharedEventDispatcher eventDispatcher,
EventDispatcher::Shared eventDispatcher,
const SharedContextContainer &contextContainer)
: ConcreteComponentDescriptor(eventDispatcher),
// TODO (39486757): implement image manager on Android, currently Android does

View File

@@ -25,7 +25,7 @@ class ParagraphComponentDescriptor final
: public ConcreteComponentDescriptor<ParagraphShadowNode> {
public:
ParagraphComponentDescriptor(
SharedEventDispatcher eventDispatcher,
EventDispatcher::Shared eventDispatcher,
const SharedContextContainer &contextContainer)
: ConcreteComponentDescriptor<ParagraphShadowNode>(eventDispatcher) {
// Every single `ParagraphShadowNode` will have a reference to
@@ -54,6 +54,7 @@ class ParagraphComponentDescriptor final
}
}
protected:
void adopt(UnsharedShadowNode shadowNode) const override {
ConcreteComponentDescriptor::adopt(shadowNode);

View File

@@ -22,6 +22,7 @@ rn_xplat_cxx_library(
("componentdescriptor", "*.h"),
("layout", "*.h"),
("shadownode", "*.h"),
("state", "*.h"),
],
prefix = "react/core",
),

View File

@@ -9,6 +9,8 @@
#include <react/core/Props.h>
#include <react/core/ShadowNode.h>
#include <react/core/State.h>
#include <react/core/StateData.h>
namespace facebook {
namespace react {
@@ -77,6 +79,20 @@ class ComponentDescriptor {
virtual SharedEventEmitter createEventEmitter(
SharedEventTarget eventTarget,
const Tag &tag) const = 0;
/*
* Create an initial State object that represents (and contains) an initial
* State's data which can be constructed based on initial Props.
*/
virtual State::Shared createInitialState(const SharedProps &props) const = 0;
/*
* Creates a new State object that represents (and contains) a new version of
* State's data.
*/
virtual State::Shared createState(
const State::Shared &previousState,
const StateData::Shared &data) const = 0;
};
} // namespace react

View File

@@ -15,6 +15,9 @@
#include <react/core/Props.h>
#include <react/core/ShadowNode.h>
#include <react/core/ShadowNodeFragment.h>
#include <react/core/State.h>
#include <react/core/StateCoordinator.h>
#include <react/core/StateUpdate.h>
namespace facebook {
namespace react {
@@ -31,14 +34,18 @@ class ConcreteComponentDescriptor : public ComponentDescriptor {
"ShadowNodeT must be a descendant of ShadowNode");
using SharedShadowNodeT = std::shared_ptr<const ShadowNodeT>;
public:
using ConcreteShadowNode = ShadowNodeT;
using ConcreteProps = typename ShadowNodeT::ConcreteProps;
using SharedConcreteProps = typename ShadowNodeT::SharedConcreteProps;
using ConcreteEventEmitter = typename ShadowNodeT::ConcreteEventEmitter;
using SharedConcreteEventEmitter =
typename ShadowNodeT::SharedConcreteEventEmitter;
using ConcreteState = typename ShadowNodeT::ConcreteState;
using ConcreteStateData = typename ShadowNodeT::ConcreteState::Data;
public:
ConcreteComponentDescriptor(SharedEventDispatcher eventDispatcher)
ConcreteComponentDescriptor(EventDispatcher::Shared eventDispatcher)
: eventDispatcher_(eventDispatcher) {}
ComponentHandle getComponentHandle() const override {
@@ -95,6 +102,32 @@ class ConcreteComponentDescriptor : public ComponentDescriptor {
std::move(eventTarget), tag, eventDispatcher_);
}
virtual State::Shared createInitialState(
const SharedProps &props) const override {
if (std::is_same<ConcreteStateData, StateData>::value) {
// Default case: Returning `null` for nodes that don't use `State`.
return nullptr;
}
return std::make_shared<ConcreteState>(
ConcreteShadowNode::initialStateData(
std::static_pointer_cast<const ConcreteProps>(props)),
std::make_shared<StateCoordinator>(eventDispatcher_));
}
virtual State::Shared createState(
const State::Shared &previousState,
const StateData::Shared &data) const override {
if (std::is_same<ConcreteStateData, StateData>::value) {
// Default case: Returning `null` for nodes that don't use `State`.
return nullptr;
}
return std::make_shared<const ConcreteState>(
std::move(*std::static_pointer_cast<ConcreteStateData>(data)),
*std::static_pointer_cast<const ConcreteState>(previousState));
}
protected:
virtual void adopt(UnsharedShadowNode shadowNode) const {
// Default implementation does nothing.
@@ -102,7 +135,7 @@ class ConcreteComponentDescriptor : public ComponentDescriptor {
}
private:
mutable SharedEventDispatcher eventDispatcher_{nullptr};
mutable EventDispatcher::Shared eventDispatcher_{nullptr};
mutable ShadowNodeCloneFunction cloneFunction_;

View File

@@ -9,8 +9,6 @@
#include "EventBeatBasedExecutor.h"
#include <cassert>
namespace facebook {
namespace react {

View File

@@ -7,7 +7,10 @@
#include "EventDispatcher.h"
#include <react/core/StateUpdate.h>
#include "BatchedEventQueue.h"
#include "RawEvent.h"
#include "UnbatchedEventQueue.h"
#define REACT_FABRIC_SYNC_EVENT_DISPATCHING_DISABLED
@@ -17,32 +20,43 @@ namespace react {
EventDispatcher::EventDispatcher(
const EventPipe &eventPipe,
const StatePipe &statePipe,
const EventBeatFactory &synchonousEventBeatFactory,
const EventBeatFactory &asynchonousEventBeatFactory) {
// Synchronous/Unbatched
eventQueues_[(int)EventPriority::SynchronousUnbatched] =
std::make_unique<UnbatchedEventQueue>(
eventPipe, synchonousEventBeatFactory());
eventPipe, statePipe, synchonousEventBeatFactory());
// Synchronous/Batched
eventQueues_[(int)EventPriority::SynchronousBatched] =
std::make_unique<BatchedEventQueue>(
eventPipe, synchonousEventBeatFactory());
eventPipe, statePipe, synchonousEventBeatFactory());
// Asynchronous/Unbatched
eventQueues_[(int)EventPriority::AsynchronousUnbatched] =
std::make_unique<UnbatchedEventQueue>(
eventPipe, asynchonousEventBeatFactory());
eventPipe, statePipe, asynchonousEventBeatFactory());
// Asynchronous/Batched
eventQueues_[(int)EventPriority::AsynchronousBatched] =
std::make_unique<BatchedEventQueue>(
eventPipe, asynchonousEventBeatFactory());
eventPipe, statePipe, asynchonousEventBeatFactory());
}
void EventDispatcher::dispatchEvent(
const RawEvent &rawEvent,
EventPriority priority) const {
getEventQueue(priority).enqueueEvent(std::move(rawEvent));
}
void EventDispatcher::dispatchStateUpdate(
StateUpdate &&stateUpdate,
EventPriority priority) const {
getEventQueue(priority).enqueueStateUpdate(std::move(stateUpdate));
}
const EventQueue &EventDispatcher::getEventQueue(EventPriority priority) const {
#ifdef REACT_FABRIC_SYNC_EVENT_DISPATCHING_DISABLED
// Synchronous dispatch works, but JavaScript interop layer does not have
// proper synchonization yet and it crashes.
@@ -55,7 +69,7 @@ void EventDispatcher::dispatchEvent(
}
#endif
eventQueues_[(int)priority]->enqueueEvent(rawEvent);
return *eventQueues_[(int)priority];
}
} // namespace react

View File

@@ -13,14 +13,13 @@
#include <react/core/EventPipe.h>
#include <react/core/EventPriority.h>
#include <react/core/EventQueue.h>
#include <react/core/RawEvent.h>
#include <react/core/StatePipe.h>
namespace facebook {
namespace react {
class EventDispatcher;
using SharedEventDispatcher = std::shared_ptr<const EventDispatcher>;
using WeakEventDispatcher = std::weak_ptr<const EventDispatcher>;
class RawEvent;
class StateUpdate;
/*
* Represents event-delivery infrastructure.
@@ -28,8 +27,12 @@ using WeakEventDispatcher = std::weak_ptr<const EventDispatcher>;
*/
class EventDispatcher {
public:
using Shared = std::shared_ptr<const EventDispatcher>;
using Weak = std::weak_ptr<const EventDispatcher>;
EventDispatcher(
const EventPipe &eventPipe,
const StatePipe &statePipe,
const EventBeatFactory &synchonousEventBeatFactory,
const EventBeatFactory &asynchonousEventBeatFactory);
@@ -38,7 +41,15 @@ class EventDispatcher {
*/
void dispatchEvent(const RawEvent &rawEvent, EventPriority priority) const;
/*
* Dispatches a state update with given priority.
*/
void dispatchStateUpdate(StateUpdate &&stateUpdate, EventPriority priority)
const;
private:
const EventQueue &getEventQueue(EventPriority priority) const;
std::array<std::unique_ptr<EventQueue>, 4> eventQueues_;
};

View File

@@ -45,7 +45,7 @@ ValueFactory EventEmitter::defaultPayloadFactory() {
EventEmitter::EventEmitter(
SharedEventTarget eventTarget,
Tag tag,
WeakEventDispatcher eventDispatcher)
EventDispatcher::Weak eventDispatcher)
: eventTarget_(std::move(eventTarget)),
eventDispatcher_(std::move(eventDispatcher)) {}

View File

@@ -42,7 +42,7 @@ class EventEmitter {
EventEmitter(
SharedEventTarget eventTarget,
Tag tag,
WeakEventDispatcher eventDispatcher);
EventDispatcher::Weak eventDispatcher);
virtual ~EventEmitter() = default;
@@ -84,7 +84,7 @@ class EventEmitter {
void toggleEventTargetOwnership_() const;
mutable SharedEventTarget eventTarget_;
WeakEventDispatcher eventDispatcher_;
EventDispatcher::Weak eventDispatcher_;
mutable int enableCounter_{0};
mutable bool isEnabled_{false};
};

View File

@@ -14,8 +14,11 @@ namespace react {
EventQueue::EventQueue(
EventPipe eventPipe,
StatePipe statePipe,
std::unique_ptr<EventBeat> eventBeat)
: eventPipe_(std::move(eventPipe)), eventBeat_(std::move(eventBeat)) {
: eventPipe_(std::move(eventPipe)),
statePipe_(std::move(statePipe)),
eventBeat_(std::move(eventBeat)) {
eventBeat_->setBeatCallback(
std::bind(&EventQueue::onBeat, this, std::placeholders::_1));
}
@@ -23,7 +26,16 @@ EventQueue::EventQueue(
void EventQueue::enqueueEvent(const RawEvent &rawEvent) const {
{
std::lock_guard<std::mutex> lock(queueMutex_);
queue_.push_back(rawEvent);
eventQueue_.push_back(rawEvent);
}
onEnqueue();
}
void EventQueue::enqueueStateUpdate(const StateUpdate &stateUpdate) const {
{
std::lock_guard<std::mutex> lock(queueMutex_);
stateUpdateQueue_.push_back(stateUpdate);
}
onEnqueue();
@@ -35,6 +47,7 @@ void EventQueue::onEnqueue() const {
void EventQueue::onBeat(jsi::Runtime &runtime) const {
flushEvents(runtime);
flushStateUpdates();
}
void EventQueue::flushEvents(jsi::Runtime &runtime) const {
@@ -43,12 +56,12 @@ void EventQueue::flushEvents(jsi::Runtime &runtime) const {
{
std::lock_guard<std::mutex> lock(queueMutex_);
if (queue_.size() == 0) {
if (eventQueue_.size() == 0) {
return;
}
queue = std::move(queue_);
queue_.clear();
queue = std::move(eventQueue_);
eventQueue_.clear();
}
{
@@ -77,5 +90,25 @@ void EventQueue::flushEvents(jsi::Runtime &runtime) const {
}
}
void EventQueue::flushStateUpdates() const {
std::vector<StateUpdate> stateUpdateQueue;
{
std::lock_guard<std::mutex> lock(queueMutex_);
if (stateUpdateQueue_.size() == 0) {
return;
}
stateUpdateQueue = std::move(stateUpdateQueue_);
stateUpdateQueue_.clear();
}
for (const auto &stateUpdate : stateUpdateQueue) {
auto pair = stateUpdate();
statePipe_(pair.second, pair.first);
}
}
} // namespace react
} // namespace facebook

View File

@@ -15,6 +15,8 @@
#include <react/core/EventBeat.h>
#include <react/core/EventPipe.h>
#include <react/core/RawEvent.h>
#include <react/core/StatePipe.h>
#include <react/core/StateUpdate.h>
namespace facebook {
namespace react {
@@ -25,7 +27,10 @@ namespace react {
*/
class EventQueue {
public:
EventQueue(EventPipe eventPipe, std::unique_ptr<EventBeat> eventBeat);
EventQueue(
EventPipe eventPipe,
StatePipe statePipe,
std::unique_ptr<EventBeat> eventBeat);
virtual ~EventQueue() = default;
/*
@@ -34,6 +39,12 @@ class EventQueue {
*/
void enqueueEvent(const RawEvent &rawEvent) const;
/*
* Enqueues and (probably later) dispatch a given state update.
* Can be called on any thread.
*/
void enqueueStateUpdate(const StateUpdate &stateUpdate) const;
protected:
/*
* Called on any enqueue operation.
@@ -44,11 +55,14 @@ class EventQueue {
void onBeat(jsi::Runtime &runtime) const;
void flushEvents(jsi::Runtime &runtime) const;
void flushStateUpdates() const;
const EventPipe eventPipe_;
const StatePipe statePipe_;
const std::unique_ptr<EventBeat> eventBeat_;
// Thread-safe, protected by `queueMutex_`.
mutable std::vector<RawEvent> queue_;
mutable std::vector<RawEvent> eventQueue_;
mutable std::vector<StateUpdate> stateUpdateQueue_;
mutable std::mutex queueMutex_;
};

View File

@@ -7,8 +7,10 @@
#pragma once
#include <react/core/ConcreteState.h>
#include <react/core/Props.h>
#include <react/core/ShadowNode.h>
#include <react/core/StateData.h>
namespace facebook {
namespace react {
@@ -23,7 +25,7 @@ template <
const char *concreteComponentName,
typename PropsT,
typename EventEmitterT = EventEmitter,
typename... Ts>
typename StateDataT = StateData>
class ConcreteShadowNode : public ShadowNode {
static_assert(
std::is_base_of<Props, PropsT>::value,
@@ -37,6 +39,8 @@ class ConcreteShadowNode : public ShadowNode {
using ConcreteEventEmitter = EventEmitterT;
using SharedConcreteEventEmitter = std::shared_ptr<const EventEmitterT>;
using SharedConcreteShadowNode = std::shared_ptr<const ConcreteShadowNode>;
using ConcreteState = ConcreteState<StateDataT>;
using ConcreteStateData = StateDataT;
static ComponentName Name() {
return ComponentName(concreteComponentName);
@@ -61,6 +65,10 @@ class ConcreteShadowNode : public ShadowNode {
return defaultSharedProps;
}
static ConcreteStateData initialStateData(const SharedConcreteProps &props) {
return {};
}
ComponentName getComponentName() const override {
return ComponentName(concreteComponentName);
}
@@ -74,6 +82,10 @@ class ConcreteShadowNode : public ShadowNode {
return std::static_pointer_cast<const PropsT>(props_);
}
const typename ConcreteState::Shared getState() const {
return std::static_pointer_cast<const ConcreteState>(state_);
}
/*
* Returns subset of children that are inherited from `SpecificShadowNodeT`.
*/

View File

@@ -32,6 +32,7 @@ ShadowNode::ShadowNode(
props_(fragment.props),
eventEmitter_(fragment.eventEmitter),
children_(fragment.children ?: emptySharedShadowNodeSharedList()),
state_(fragment.state),
cloneFunction_(cloneFunction),
childrenAreShared_(true),
revision_(1) {
@@ -48,6 +49,7 @@ ShadowNode::ShadowNode(
eventEmitter_(fragment.eventEmitter ?: sourceShadowNode.eventEmitter_),
children_(fragment.children ?: sourceShadowNode.children_),
localData_(fragment.localData ?: sourceShadowNode.localData_),
state_(fragment.state ?: sourceShadowNode.getCommitedState()),
cloneFunction_(sourceShadowNode.cloneFunction_),
childrenAreShared_(true),
revision_(sourceShadowNode.revision_ + 1) {
@@ -82,6 +84,15 @@ Tag ShadowNode::getRootTag() const {
return rootTag_;
}
const State::Shared &ShadowNode::getState() const {
return state_;
}
const State::Shared &ShadowNode::getCommitedState() const {
return state_ ? state_->getCommitedState()
: ShadowNodeFragment::statePlaceholder();
}
SharedLocalData ShadowNode::getLocalData() const {
return localData_;
}
@@ -148,6 +159,9 @@ void ShadowNode::cloneChildrenIfShared() {
void ShadowNode::setMounted(bool mounted) const {
eventEmitter_->setEnabled(mounted);
if (mounted && state_) {
state_->commit(*this);
}
}
bool ShadowNode::constructAncestorPath(

View File

@@ -16,6 +16,7 @@
#include <react/core/Props.h>
#include <react/core/ReactPrimitives.h>
#include <react/core/Sealable.h>
#include <react/core/State.h>
#include <react/debug/DebugStringConvertible.h>
namespace facebook {
@@ -26,6 +27,7 @@ struct ShadowNodeFragment;
class ShadowNode;
using SharedShadowNode = std::shared_ptr<const ShadowNode>;
using WeakShadowNode = std::weak_ptr<const ShadowNode>;
using UnsharedShadowNode = std::shared_ptr<ShadowNode>;
using SharedShadowNodeList = std::vector<SharedShadowNode>;
using SharedShadowNodeSharedList = std::shared_ptr<const SharedShadowNodeList>;
@@ -39,6 +41,9 @@ class ShadowNode : public virtual Sealable,
public virtual DebugStringConvertible,
public std::enable_shared_from_this<ShadowNode> {
public:
using Shared = std::shared_ptr<const ShadowNode>;
using Weak = std::weak_ptr<const ShadowNode>;
static SharedShadowNodeSharedList emptySharedShadowNodeSharedList();
#pragma mark - Constructors
@@ -76,6 +81,17 @@ class ShadowNode : public virtual Sealable,
Tag getTag() const;
Tag getRootTag() const;
/*
* Returns a state associated with the particular node.
*/
const State::Shared &getState() const;
/*
* Returns a momentary value of currently committed state associated with a
* family of nodes which this node belongs to.
*/
const State::Shared &getCommitedState() const;
/*
* Returns a local data associated with the node.
* `LocalData` object might be used for data exchange between native view and
@@ -139,6 +155,7 @@ class ShadowNode : public virtual Sealable,
SharedEventEmitter eventEmitter_;
SharedShadowNodeSharedList children_;
SharedLocalData localData_;
State::Shared state_;
private:
/*

View File

@@ -38,5 +38,10 @@ SharedLocalData &ShadowNodeFragment::localDataPlaceholder() {
return instance;
}
State::Shared &ShadowNodeFragment::statePlaceholder() {
static auto &instance = *new State::Shared();
return instance;
}
} // namespace react
} // namespace facebook

View File

@@ -12,6 +12,7 @@
#include <react/core/Props.h>
#include <react/core/ReactPrimitives.h>
#include <react/core/ShadowNode.h>
#include <react/core/State.h>
namespace facebook {
namespace react {
@@ -20,7 +21,7 @@ namespace react {
* An object which supposed to be used as a parameter specifying a shape
* of created or cloned ShadowNode.
* Note: Most of the fields are `const &` references (essentially just raw
* pointers) which means that the Fragment does not copy/store them or
* pointers) which means that the Fragment does not copy/store them nor
* retain ownership of them.
*/
struct ShadowNodeFragment {
@@ -30,6 +31,7 @@ struct ShadowNodeFragment {
const SharedEventEmitter &eventEmitter = eventEmitterPlaceholder();
const SharedShadowNodeSharedList &children = childrenPlaceholder();
const SharedLocalData &localData = localDataPlaceholder();
const State::Shared &state = statePlaceholder();
static Tag tagPlaceholder();
static Tag surfaceIdPlaceholder();
@@ -37,6 +39,7 @@ struct ShadowNodeFragment {
static SharedEventEmitter &eventEmitterPlaceholder();
static SharedShadowNodeSharedList &childrenPlaceholder();
static SharedLocalData &localDataPlaceholder();
static State::Shared &statePlaceholder();
};
} // namespace react

View File

@@ -0,0 +1,88 @@
/**
* 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/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<const ConcreteState>;
using Data = DataT;
ConcreteState(Data &&data, StateCoordinator::Shared stateCoordinator)
: State(std::move(stateCoordinator)), data_(std::move(data)) {}
ConcreteState(Data &&data, const ConcreteState &other)
: State(other.stateCoordinator_), data_(std::move(data)) {}
/*
* Returns stored data.
*/
const Data &getData() const {
return 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,
EventPriority priority = EventPriority::SynchronousUnbatched) const {
updateState(
[data = std::move(newData)](const Data &oldData) mutable -> Data && {
return std::move(data);
});
}
/*
* 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 && (const Data &oldData)> callback,
EventPriority priority = EventPriority::AsynchronousBatched) const {
stateCoordinator_->dispatchRawState(
{[stateCoordinator = stateCoordinator_,
callback = std::move(
callback)]() -> std::pair<StateTarget, StateData::Shared> {
auto target = stateCoordinator->getTarget();
auto oldState = target.getShadowNode().getState();
auto oldData = std::static_pointer_cast<const ConcreteState>(oldState)
->getData();
auto newData = std::make_shared<Data>(callback(oldData));
return {std::move(target), std::move(newData)};
}},
priority);
}
private:
DataT data_;
};
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,30 @@
/**
* 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 "State.h"
#include <react/core/ShadowNode.h>
#include <react/core/State.h>
#include <react/core/StateTarget.h>
#include <react/core/StateUpdate.h>
namespace facebook {
namespace react {
State::State(StateCoordinator::Shared stateCoordinator)
: stateCoordinator_(std::move(stateCoordinator)){};
void State::commit(const ShadowNode &shadowNode) const {
stateCoordinator_->setTarget(StateTarget{shadowNode});
}
const State::Shared &State::getCommitedState() const {
return stateCoordinator_->getTarget().getShadowNode().getState();
}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,48 @@
/**
* 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 <react/core/StateCoordinator.h>
namespace facebook {
namespace react {
class ShadowNode;
/*
* An abstract interface of State.
* State is used to control and continuously advance a single vision of some
* state (arbitrary data) associated with a family of shadow nodes.
*/
class State {
public:
using Shared = std::shared_ptr<const State>;
State(StateCoordinator::Shared stateCoordinator);
virtual ~State() = default;
protected:
StateCoordinator::Shared stateCoordinator_;
private:
friend class ShadowNode;
friend class StateCoordinator;
/*
* Must be used by `ShadowNode` *only*.
*/
void commit(const ShadowNode &shadowNode) const;
/*
* Must be used by `ShadowNode` *only*.
*/
const State::Shared &getCommitedState() const;
};
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,42 @@
/**
* 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 "StateCoordinator.h"
#include <react/core/ShadowNode.h>
#include <react/core/StateData.h>
#include <react/core/StateUpdate.h>
namespace facebook {
namespace react {
StateCoordinator::StateCoordinator(EventDispatcher::Weak eventDispatcher)
: eventDispatcher_(eventDispatcher) {}
const StateTarget &StateCoordinator::getTarget() const {
std::shared_lock<better::shared_mutex> lock(mutex_);
return target_;
}
void StateCoordinator::setTarget(StateTarget &&target) const {
std::unique_lock<better::shared_mutex> lock(mutex_);
target_ = std::move(target);
}
void StateCoordinator::dispatchRawState(
StateUpdate &&stateUpdate,
EventPriority priority) const {
auto eventDispatcher = eventDispatcher_.lock();
if (!eventDispatcher || !target_) {
return;
}
eventDispatcher->dispatchStateUpdate(std::move(stateUpdate), priority);
}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,50 @@
/**
* 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/mutex.h>
#include <mutex>
#include <react/core/EventDispatcher.h>
#include <react/core/StateTarget.h>
namespace facebook {
namespace react {
class ShadowNode;
/*
* Coordinates a vision of the same state values between shadow nodes from
* the same family.
*/
class StateCoordinator {
public:
using Shared = std::shared_ptr<const StateCoordinator>;
StateCoordinator(EventDispatcher::Weak eventDispatcher);
/*
* Dispatches a state update with given priority.
*/
void dispatchRawState(StateUpdate &&stateUpdate, EventPriority priority)
const;
/*
* Sets and gets a state target.
*/
const StateTarget &getTarget() const;
void setTarget(StateTarget &&target) const;
private:
EventDispatcher::Weak eventDispatcher_;
mutable StateTarget target_{}; // Protected by `mutex_`.
mutable better::shared_mutex mutex_;
};
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,24 @@
/**
* 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 <memory>
namespace facebook {
namespace react {
/*
* Dummy type that is used as a placeholder for state data for nodes that
* don't have a state.
*/
struct StateData {
using Shared = std::shared_ptr<void>;
};
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,22 @@
/**
* 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 <react/core/StateData.h>
#include <react/core/StateTarget.h>
namespace facebook {
namespace react {
using StatePipe = std::function<
void(const StateData::Shared &data, const StateTarget &target)>;
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,28 @@
/**
* 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 "StateTarget.h"
#include <react/core/ShadowNode.h>
namespace facebook {
namespace react {
StateTarget::StateTarget() : shadowNode_(nullptr) {}
StateTarget::StateTarget(const ShadowNode &shadowNode)
: shadowNode_(shadowNode.shared_from_this()) {}
StateTarget::operator bool() const {
return (bool)shadowNode_;
}
const ShadowNode &StateTarget::getShadowNode() const {
return *std::static_pointer_cast<const ShadowNode>(shadowNode_);
}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,57 @@
/**
* 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 <memory>
namespace facebook {
namespace react {
class ShadowNode;
/*
* Represents an entity that receives state update.
* Practically, just a wrapper around shared a pointer to ShadowNode. We need
* this mostly to avoid circular dependency problems.
*/
class StateTarget {
public:
/*
* Creates an empty target.
*/
StateTarget();
/*
* Creates a target which points to a given `ShadowNode`.
*/
StateTarget(const ShadowNode &shadowNode);
/*
* Copyable and moveable.
*/
StateTarget(const StateTarget &other) = default;
StateTarget &operator=(const StateTarget &other) = default;
StateTarget(StateTarget &&other) noexcept = default;
StateTarget &operator=(StateTarget &&other) = default;
/*
* Returns `true` is the target is not empty.
*/
operator bool() const;
/*
* Returns a reference to a stored `ShadowNode`.
*/
const ShadowNode &getShadowNode() const;
private:
std::shared_ptr<const void> shadowNode_;
};
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,18 @@
/**
* 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 "StateUpdate.h"
namespace facebook {
namespace react {
std::pair<StateTarget, StateData::Shared> StateUpdate::operator()() const {
return callback_();
}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,34 @@
/**
* 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 <react/core/StateData.h>
#include <react/core/StateTarget.h>
namespace facebook {
namespace react {
/*
* Carries some logic and additional information about state update transaction.
*/
class StateUpdate {
public:
std::pair<StateTarget, StateData::Shared> operator()() const;
/*
* The current implementation simply uses `std::function` inside that captures
* everything which is needed to perform state update. That will be probably
* changed in the future.
*/
std::function<std::pair<StateTarget, StateData::Shared>()> callback_;
};
} // namespace react
} // namespace facebook

View File

@@ -24,7 +24,8 @@ ShadowView::ShadowView(const ShadowNode &shadowNode)
props(shadowNode.getProps()),
eventEmitter(shadowNode.getEventEmitter()),
layoutMetrics(layoutMetricsFromShadowNode(shadowNode)),
localData(shadowNode.getLocalData()) {}
localData(shadowNode.getLocalData()),
state(shadowNode.getState()) {}
bool ShadowView::operator==(const ShadowView &rhs) const {
return std::tie(
@@ -33,14 +34,16 @@ bool ShadowView::operator==(const ShadowView &rhs) const {
this->props,
this->eventEmitter,
this->layoutMetrics,
this->localData) ==
this->localData,
this->state) ==
std::tie(
rhs.tag,
rhs.componentName,
rhs.props,
rhs.eventEmitter,
rhs.layoutMetrics,
rhs.localData);
rhs.localData,
rhs.state);
}
bool ShadowView::operator!=(const ShadowView &rhs) const {

View File

@@ -40,6 +40,7 @@ struct ShadowView final {
SharedEventEmitter eventEmitter = {};
LayoutMetrics layoutMetrics = EmptyLayoutMetrics;
SharedLocalData localData = {};
State::Shared state = {};
};
/*

View File

@@ -16,7 +16,7 @@ namespace react {
* This is a sample implementation. Each app should provide its own.
*/
ComponentRegistryFactory getDefaultComponentRegistryFactory() {
return [](const SharedEventDispatcher &eventDispatcher,
return [](const EventDispatcher::Shared &eventDispatcher,
const SharedContextContainer &contextContainer) {
auto registry = std::make_shared<ComponentDescriptorRegistry>();
return registry;

View File

@@ -25,7 +25,7 @@ namespace react {
*/
using ComponentRegistryFactory =
std::function<SharedComponentDescriptorRegistry(
const SharedEventDispatcher &eventDispatcher,
const EventDispatcher::Shared &eventDispatcher,
const SharedContextContainer &contextContainer)>;
ComponentRegistryFactory getDefaultComponentRegistryFactory();

View File

@@ -45,8 +45,18 @@ Scheduler::Scheduler(
uiManagerBinding->dispatchEvent(runtime, eventTarget, type, payloadFactory);
};
auto statePipe = [uiManager = &uiManagerRef](
const StateData::Shared &data,
const StateTarget &stateTarget) {
uiManager->updateState(
stateTarget.getShadowNode().shared_from_this(), data);
};
auto eventDispatcher = std::make_shared<EventDispatcher>(
eventPipe, synchronousEventBeatFactory, asynchronousEventBeatFactory);
eventPipe,
statePipe,
synchronousEventBeatFactory,
asynchronousEventBeatFactory);
componentDescriptorRegistry_ =
buildRegistryFunction(eventDispatcher, contextContainer);

View File

@@ -18,13 +18,16 @@ SharedShadowNode UIManager::createNode(
SystraceSection s("UIManager::createNode");
auto &componentDescriptor = componentDescriptorRegistry_->at(name);
const auto &props = componentDescriptor.cloneProps(nullptr, rawProps);
const auto &state = componentDescriptor.createInitialState(props);
auto shadowNode = componentDescriptor.createShadowNode(
{.tag = tag,
.rootTag = surfaceId,
.eventEmitter =
componentDescriptor.createEventEmitter(std::move(eventTarget), tag),
.props = componentDescriptor.cloneProps(nullptr, rawProps)});
.props = props,
.state = state});
if (delegate_) {
delegate_->uiManagerDidCreateShadowNode(shadowNode);
@@ -129,6 +132,27 @@ LayoutMetrics UIManager::getRelativeLayoutMetrics(
*layoutableAncestorShadowNode);
}
void UIManager::updateState(
const SharedShadowNode &shadowNode,
const StateData::Shared &rawStateData) const {
long startCommitTime = getTime();
auto &componentDescriptor =
componentDescriptorRegistry_->at(shadowNode->getComponentHandle());
auto state =
componentDescriptor.createState(shadowNode->getState(), rawStateData);
auto newShadowNode = shadowNode->clone(ShadowNodeFragment{.state = state});
shadowTreeRegistry_->visit(
shadowNode->getRootTag(), [&](const ShadowTree &shadowTree) {
shadowTree.tryCommit(
[&](const SharedRootShadowNode &oldRootShadowNode) {
return oldRootShadowNode->clone(shadowNode, newShadowNode);
},
startCommitTime);
});
}
void UIManager::setShadowTreeRegistry(ShadowTreeRegistry *shadowTreeRegistry) {
shadowTreeRegistry_ = shadowTreeRegistry;
}

View File

@@ -7,6 +7,7 @@
#include <jsi/jsi.h>
#include <react/core/ShadowNode.h>
#include <react/core/StateData.h>
#include <react/uimanager/ComponentDescriptorRegistry.h>
#include <react/uimanager/ShadowTreeRegistry.h>
#include <react/uimanager/UIManagerDelegate.h>
@@ -31,6 +32,7 @@ class UIManager {
private:
friend class UIManagerBinding;
friend class Scheduler;
SharedShadowNode createNode(
Tag tag,
@@ -65,6 +67,14 @@ class UIManager {
const ShadowNode &shadowNode,
const ShadowNode *ancestorShadowNode) const;
/*
* Creates a new shadow node with given state data, clones what's necessary
* and performs a commit.
*/
void updateState(
const SharedShadowNode &shadowNode,
const StateData::Shared &rawStateData) const;
ShadowTreeRegistry *shadowTreeRegistry_;
SharedComponentDescriptorRegistry componentDescriptorRegistry_;
UIManagerDelegate *delegate_;

View File

@@ -30,7 +30,7 @@ namespace react {
// TODO (T29441913): Codegen this app-specific implementation.
ComponentRegistryFactory getDefaultComponentRegistryFactory() {
return [](const SharedEventDispatcher &eventDispatcher,
return [](const EventDispatcher::Shared &eventDispatcher,
const SharedContextContainer &contextContainer) {
auto registry = std::make_shared<ComponentDescriptorRegistry>();
registry->registerComponentDescriptor(