feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,86 @@
#include "AnimatedSensorModule.h"
#include <utility>
namespace reanimated {
AnimatedSensorModule::AnimatedSensorModule(
const PlatformDepMethodsHolder &platformDepMethodsHolder)
: platformRegisterSensorFunction_(platformDepMethodsHolder.registerSensor),
platformUnregisterSensorFunction_(
platformDepMethodsHolder.unregisterSensor) {}
AnimatedSensorModule::~AnimatedSensorModule() {
assert(sensorsIds_.empty());
}
jsi::Value AnimatedSensorModule::registerSensor(
jsi::Runtime &rt,
const std::shared_ptr<WorkletRuntime> &uiWorkletRuntime,
const jsi::Value &sensorTypeValue,
const jsi::Value &interval,
const jsi::Value &iosReferenceFrame,
const jsi::Value &sensorDataHandler) {
SensorType sensorType = static_cast<SensorType>(sensorTypeValue.asNumber());
auto shareableHandler = extractShareableOrThrow<ShareableWorklet>(
rt,
sensorDataHandler,
"[Reanimated] Sensor event handler must be a worklet.");
int sensorId = platformRegisterSensorFunction_(
sensorType,
interval.asNumber(),
iosReferenceFrame.asNumber(),
[sensorType,
shareableHandler,
weakUiWorkletRuntime = std::weak_ptr<WorkletRuntime>(uiWorkletRuntime)](
double newValues[], int orientationDegrees) {
auto uiWorkletRuntime = weakUiWorkletRuntime.lock();
if (uiWorkletRuntime == nullptr) {
return;
}
jsi::Runtime &uiRuntime = uiWorkletRuntime->getJSIRuntime();
jsi::Object value(uiRuntime);
if (sensorType == SensorType::ROTATION_VECTOR) {
// TODO: timestamp should be provided by the platform implementation
// such that the native side has a chance of providing a true event
// timestamp
value.setProperty(uiRuntime, "qx", newValues[0]);
value.setProperty(uiRuntime, "qy", newValues[1]);
value.setProperty(uiRuntime, "qz", newValues[2]);
value.setProperty(uiRuntime, "qw", newValues[3]);
value.setProperty(uiRuntime, "yaw", newValues[4]);
value.setProperty(uiRuntime, "pitch", newValues[5]);
value.setProperty(uiRuntime, "roll", newValues[6]);
} else {
value.setProperty(uiRuntime, "x", newValues[0]);
value.setProperty(uiRuntime, "y", newValues[1]);
value.setProperty(uiRuntime, "z", newValues[2]);
}
value.setProperty(
uiRuntime, "interfaceOrientation", orientationDegrees);
uiWorkletRuntime->runGuarded(shareableHandler, value);
});
if (sensorId != -1) {
sensorsIds_.insert(sensorId);
}
return jsi::Value(sensorId);
}
void AnimatedSensorModule::unregisterSensor(const jsi::Value &sensorId) {
// It is called during sensor hook unmounting
sensorsIds_.erase(sensorId.getNumber());
platformUnregisterSensorFunction_(sensorId.asNumber());
}
void AnimatedSensorModule::unregisterAllSensors() {
for (auto sensorId : sensorsIds_) {
platformUnregisterSensorFunction_(sensorId);
}
sensorsIds_.clear();
}
} // namespace reanimated

View File

@@ -0,0 +1,44 @@
#pragma once
#include <jsi/jsi.h>
#include <memory>
#include <unordered_set>
#include "PlatformDepMethodsHolder.h"
#include "Shareables.h"
#include "WorkletRuntime.h"
namespace reanimated {
using namespace facebook;
enum SensorType {
ACCELEROMETER = 1,
GYROSCOPE = 2,
GRAVITY = 3,
MAGNETIC_FIELD = 4,
ROTATION_VECTOR = 5,
};
class AnimatedSensorModule {
std::unordered_set<int> sensorsIds_;
RegisterSensorFunction platformRegisterSensorFunction_;
UnregisterSensorFunction platformUnregisterSensorFunction_;
public:
AnimatedSensorModule(
const PlatformDepMethodsHolder &platformDepMethodsHolder);
~AnimatedSensorModule();
jsi::Value registerSensor(
jsi::Runtime &rt,
const std::shared_ptr<WorkletRuntime> &uiWorkletRuntime,
const jsi::Value &sensorType,
const jsi::Value &interval,
const jsi::Value &iosReferenceFrame,
const jsi::Value &sensorDataContainer);
void unregisterSensor(const jsi::Value &sensorId);
void unregisterAllSensors();
};
} // namespace reanimated

View File

@@ -0,0 +1,41 @@
#ifdef RCT_NEW_ARCH_ENABLED
#include "PropsRegistry.h"
namespace reanimated {
std::lock_guard<std::mutex> PropsRegistry::createLock() const {
return std::lock_guard<std::mutex>(mutex_);
}
void PropsRegistry::update(
const ShadowNode::Shared &shadowNode,
folly::dynamic &&props) {
const auto tag = shadowNode->getTag();
const auto it = map_.find(tag);
if (it == map_.cend()) {
// we need to store ShadowNode because `ShadowNode::getFamily`
// returns `ShadowNodeFamily const &` which is non-owning
map_[tag] = std::make_pair(shadowNode, props);
} else {
// no need to update `.first` because ShadowNode's family never changes
// merge new props with old props
it->second.second.update(props);
}
}
void PropsRegistry::for_each(std::function<void(
const ShadowNodeFamily &family,
const folly::dynamic &props)> callback) const {
for (const auto &[_, value] : map_) {
callback(value.first->getFamily(), value.second);
}
}
void PropsRegistry::remove(const Tag tag) {
map_.erase(tag);
}
} // namespace reanimated
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,58 @@
#pragma once
#ifdef RCT_NEW_ARCH_ENABLED
#include <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/core/ShadowNode.h>
#include <unordered_map>
#include <utility>
using namespace facebook;
using namespace react;
namespace reanimated {
class PropsRegistry {
public:
std::lock_guard<std::mutex> createLock() const;
// returns a lock you need to hold when calling any of the methods below
void update(const ShadowNode::Shared &shadowNode, folly::dynamic &&props);
void for_each(std::function<void(
const ShadowNodeFamily &family,
const folly::dynamic &props)> callback) const;
void remove(const Tag tag);
void pleaseSkipReanimatedCommit() {
shouldReanimatedSkipCommit_ = true;
}
bool shouldReanimatedSkipCommit() {
#if REACT_NATIVE_MINOR_VERSION >= 73
// In RN 0.73+ we have a mount hook that will properly unset this flag
// after a non-Reanimated commit.
return shouldReanimatedSkipCommit_;
#else
return shouldReanimatedSkipCommit_.exchange(false);
#endif
}
#if REACT_NATIVE_MINOR_VERSION >= 73
void resetReanimatedSkipCommitFlag() {
shouldReanimatedSkipCommit_ = false;
}
#endif
private:
std::unordered_map<Tag, std::pair<ShadowNode::Shared, folly::dynamic>> map_;
mutable std::mutex mutex_; // Protects `map_`.
std::atomic<bool> shouldReanimatedSkipCommit_;
};
} // namespace reanimated
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,71 @@
#ifdef RCT_NEW_ARCH_ENABLED
#include <react/renderer/core/ComponentDescriptor.h>
#include "ReanimatedCommitHook.h"
#include "ReanimatedCommitMarker.h"
#include "ShadowTreeCloner.h"
using namespace facebook::react;
namespace reanimated {
ReanimatedCommitHook::ReanimatedCommitHook(
const std::shared_ptr<PropsRegistry> &propsRegistry,
const std::shared_ptr<UIManager> &uiManager)
: propsRegistry_(propsRegistry), uiManager_(uiManager) {
uiManager_->registerCommitHook(*this);
}
ReanimatedCommitHook::~ReanimatedCommitHook() noexcept {
uiManager_->unregisterCommitHook(*this);
}
RootShadowNode::Unshared ReanimatedCommitHook::shadowTreeWillCommit(
ShadowTree const &,
RootShadowNode::Shared const &,
#if REACT_NATIVE_MINOR_VERSION >= 73
RootShadowNode::Unshared const &newRootShadowNode) noexcept {
#else
RootShadowNode::Unshared const &newRootShadowNode) const noexcept {
#endif
if (ReanimatedCommitMarker::isReanimatedCommit()) {
// ShadowTree commited by Reanimated, no need to apply updates from
// PropsRegistry
return newRootShadowNode;
}
// ShadowTree not commited by Reanimated, apply updates from PropsRegistry
auto rootNode = newRootShadowNode->ShadowNode::clone(ShadowNodeFragment{});
{
auto lock = propsRegistry_->createLock();
propsRegistry_->for_each(
[&](const ShadowNodeFamily &family, const folly::dynamic &props) {
auto newRootNode =
cloneShadowTreeWithNewProps(rootNode, family, RawProps(props));
if (newRootNode == nullptr) {
// this happens when React removed the component but Reanimated
// still tries to animate it, let's skip update for this specific
// component
return;
}
rootNode = newRootNode;
});
}
// If the commit comes from React Native then skip one commit from Reanimated
// since the ShadowTree to be committed by Reanimated may not include the new
// changes from React Native yet and all changes of animated props will be
// applied in ReanimatedCommitHook by iterating over PropsRegistry.
propsRegistry_->pleaseSkipReanimatedCommit();
return std::static_pointer_cast<RootShadowNode>(rootNode);
}
} // namespace reanimated
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,51 @@
#pragma once
#ifdef RCT_NEW_ARCH_ENABLED
#include <react/renderer/uimanager/UIManagerCommitHook.h>
#include <memory>
#include "PropsRegistry.h"
using namespace facebook::react;
namespace reanimated {
class ReanimatedCommitHook : public UIManagerCommitHook {
public:
ReanimatedCommitHook(
const std::shared_ptr<PropsRegistry> &propsRegistry,
const std::shared_ptr<UIManager> &uiManager);
~ReanimatedCommitHook() noexcept override;
#if REACT_NATIVE_MINOR_VERSION >= 73
void commitHookWasRegistered(UIManager const &) noexcept override {}
void commitHookWasUnregistered(UIManager const &) noexcept override {}
RootShadowNode::Unshared shadowTreeWillCommit(
ShadowTree const &shadowTree,
RootShadowNode::Shared const &oldRootShadowNode,
RootShadowNode::Unshared const &newRootShadowNode) noexcept override;
#else
void commitHookWasRegistered(UIManager const &) const noexcept override {}
void commitHookWasUnregistered(UIManager const &) const noexcept override {}
RootShadowNode::Unshared shadowTreeWillCommit(
ShadowTree const &shadowTree,
RootShadowNode::Shared const &oldRootShadowNode,
RootShadowNode::Unshared const &newRootShadowNode)
const noexcept override;
#endif
private:
std::shared_ptr<PropsRegistry> propsRegistry_;
std::shared_ptr<UIManager> uiManager_;
};
} // namespace reanimated
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,26 @@
#ifdef RCT_NEW_ARCH_ENABLED
#include "ReanimatedCommitMarker.h"
#include <react/debug/react_native_assert.h>
namespace reanimated {
thread_local bool ReanimatedCommitMarker::reanimatedCommitFlag_{false};
ReanimatedCommitMarker::ReanimatedCommitMarker() {
react_native_assert(reanimatedCommitFlag_ != true);
reanimatedCommitFlag_ = true;
}
ReanimatedCommitMarker::~ReanimatedCommitMarker() {
reanimatedCommitFlag_ = false;
}
bool ReanimatedCommitMarker::isReanimatedCommit() {
return reanimatedCommitFlag_;
}
} // namespace reanimated
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,23 @@
#pragma once
#ifdef RCT_NEW_ARCH_ENABLED
namespace reanimated {
// This class is used to mark shadow tree commit as coming from Reanimated.
// During the life of this object, isReanimatedCommit() will return true, false
// otherwise. isReanimatedCommit() value change is restricted to the thread that
// created the object.
class ReanimatedCommitMarker {
public:
ReanimatedCommitMarker();
~ReanimatedCommitMarker();
static bool isReanimatedCommit();
private:
static thread_local bool reanimatedCommitFlag_;
};
} // namespace reanimated
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,31 @@
#if defined(RCT_NEW_ARCH_ENABLED) && REACT_NATIVE_MINOR_VERSION >= 73
#include "ReanimatedMountHook.h"
#include "ReanimatedCommitMarker.h"
namespace reanimated {
ReanimatedMountHook::ReanimatedMountHook(
const std::shared_ptr<PropsRegistry> &propsRegistry,
const std::shared_ptr<UIManager> &uiManager)
: propsRegistry_(propsRegistry), uiManager_(uiManager) {
uiManager_->registerMountHook(*this);
}
ReanimatedMountHook::~ReanimatedMountHook() noexcept {
uiManager_->unregisterMountHook(*this);
}
void ReanimatedMountHook::shadowTreeDidMount(
RootShadowNode::Shared const &,
double) noexcept {
// When commit from React Native has finished, we reset the skip commit flag
// in order to allow Reanimated to commit its tree
if (!ReanimatedCommitMarker::isReanimatedCommit()) {
propsRegistry_->resetReanimatedSkipCommitFlag();
}
}
} // namespace reanimated
#endif // defined(RCT_NEW_ARCH_ENABLED) && REACT_NATIVE_MINOR_VERSION >= 73

View File

@@ -0,0 +1,33 @@
#pragma once
#if defined(RCT_NEW_ARCH_ENABLED) && REACT_NATIVE_MINOR_VERSION >= 73
#include "PropsRegistry.h"
#include <react/renderer/uimanager/UIManagerMountHook.h>
#include <memory>
namespace reanimated {
using namespace facebook::react;
class ReanimatedMountHook : public UIManagerMountHook {
public:
ReanimatedMountHook(
const std::shared_ptr<PropsRegistry> &propsRegistry,
const std::shared_ptr<UIManager> &uiManager);
~ReanimatedMountHook() noexcept override;
void shadowTreeDidMount(
RootShadowNode::Shared const &rootShadowNode,
double mountTime) noexcept override;
private:
const std::shared_ptr<PropsRegistry> propsRegistry_;
const std::shared_ptr<UIManager> uiManager_;
};
} // namespace reanimated
#endif // defined(RCT_NEW_ARCH_ENABLED) && REACT_NATIVE_MINOR_VERSION >= 73

View File

@@ -0,0 +1,65 @@
#ifdef RCT_NEW_ARCH_ENABLED
#include <utility>
#include "ShadowTreeCloner.h"
namespace reanimated {
ShadowNode::Unshared cloneShadowTreeWithNewProps(
const ShadowNode::Shared &oldRootNode,
const ShadowNodeFamily &family,
RawProps &&rawProps) {
// adapted from ShadowNode::cloneTree
auto ancestors = family.getAncestors(*oldRootNode);
if (ancestors.empty()) {
return ShadowNode::Unshared{nullptr};
}
auto &parent = ancestors.back();
auto &source = parent.first.get().getChildren().at(parent.second);
PropsParserContext propsParserContext{
source->getSurfaceId(), *source->getContextContainer()};
const auto props = source->getComponentDescriptor().cloneProps(
propsParserContext, source->getProps(), std::move(rawProps));
auto newChildNode = source->clone({/* .props = */ props, ShadowNodeFragment::childrenPlaceholder(), source->getState()});
for (auto it = ancestors.rbegin(); it != ancestors.rend(); ++it) {
auto &parentNode = it->first.get();
auto childIndex = it->second;
auto children = parentNode.getChildren();
const auto &oldChildNode = *children.at(childIndex);
react_native_assert(ShadowNode::sameFamily(oldChildNode, *newChildNode));
if (!parentNode.getSealed()) {
// Optimization: if a ShadowNode is unsealed, we can directly update its
// children instead of cloning the whole path to the root node.
auto &parentNodeNonConst = const_cast<ShadowNode &>(parentNode);
parentNodeNonConst.replaceChild(oldChildNode, newChildNode, childIndex);
// Unfortunately, `replaceChild` does not update Yoga nodes, so we need to
// update them manually here.
static_cast<YogaLayoutableShadowNode *>(&parentNodeNonConst)
->updateYogaChildren();
return std::const_pointer_cast<ShadowNode>(oldRootNode);
}
children[childIndex] = newChildNode;
newChildNode = parentNode.clone({
ShadowNodeFragment::propsPlaceholder(),
std::make_shared<ShadowNode::ListOfShared>(children),
parentNode.getState()
});
}
return std::const_pointer_cast<ShadowNode>(newChildNode);
}
} // namespace reanimated
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,22 @@
#pragma once
#ifdef RCT_NEW_ARCH_ENABLED
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/uimanager/UIManager.h>
#include <memory>
#include <set>
using namespace facebook;
using namespace react;
namespace reanimated {
ShadowNode::Unshared cloneShadowTreeWithNewProps(
const ShadowNode::Shared &oldRootNode,
const ShadowNodeFamily &family,
RawProps &&rawProps);
} // namespace reanimated
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,9 @@
#pragma once
typedef enum LayoutAnimationType {
ENTERING = 1,
EXITING = 2,
LAYOUT = 3,
SHARED_ELEMENT_TRANSITION = 4,
SHARED_ELEMENT_TRANSITION_PROGRESS = 5,
} LayoutAnimationType;

View File

@@ -0,0 +1,205 @@
#include "LayoutAnimationsManager.h"
#include "CollectionUtils.h"
#include "Shareables.h"
#ifndef NDEBUG
#include <utility>
#endif
namespace reanimated {
void LayoutAnimationsManager::configureAnimationBatch(
const std::vector<LayoutAnimationConfig> &layoutAnimationsBatch) {
auto lock = std::unique_lock<std::recursive_mutex>(animationsMutex_);
std::vector<LayoutAnimationConfig> sharedTransitionConfigs;
for (auto layoutAnimationConfig : layoutAnimationsBatch) {
const auto &[tag, type, config, sharedTransitionTag] =
layoutAnimationConfig;
if (type == SHARED_ELEMENT_TRANSITION ||
type == SHARED_ELEMENT_TRANSITION_PROGRESS) {
clearSharedTransitionConfig(tag);
sharedTransitionConfigs.push_back(std::move(layoutAnimationConfig));
} else {
if (config == nullptr) {
getConfigsForType(type).erase(tag);
} else {
getConfigsForType(type)[tag] = config;
}
}
}
for (const auto &[tag, type, config, sharedTransitionTag] :
sharedTransitionConfigs) {
if (config == nullptr) {
continue;
}
sharedTransitionGroups_[sharedTransitionTag].push_back(tag);
viewTagToSharedTag_[tag] = sharedTransitionTag;
getConfigsForType(SHARED_ELEMENT_TRANSITION)[tag] = config;
if (type == SHARED_ELEMENT_TRANSITION) {
ignoreProgressAnimationForTag_.insert(tag);
}
}
}
void LayoutAnimationsManager::setShouldAnimateExiting(
const int tag,
const bool value) {
auto lock = std::unique_lock<std::recursive_mutex>(animationsMutex_);
shouldAnimateExitingForTag_[tag] = value;
}
bool LayoutAnimationsManager::shouldAnimateExiting(
const int tag,
const bool shouldAnimate) {
auto lock = std::unique_lock<std::recursive_mutex>(animationsMutex_);
return collection::contains(shouldAnimateExitingForTag_, tag)
? shouldAnimateExitingForTag_[tag]
: shouldAnimate;
}
bool LayoutAnimationsManager::hasLayoutAnimation(
const int tag,
const LayoutAnimationType type) {
auto lock = std::unique_lock<std::recursive_mutex>(animationsMutex_);
if (type == SHARED_ELEMENT_TRANSITION_PROGRESS) {
auto end = ignoreProgressAnimationForTag_.end();
return ignoreProgressAnimationForTag_.find(tag) == end;
}
return collection::contains(getConfigsForType(type), tag);
}
void LayoutAnimationsManager::clearSharedTransitionConfig(const int tag) {
auto lock = std::unique_lock<std::recursive_mutex>(animationsMutex_);
#ifndef NDEBUG
const auto &pair = viewsScreenSharedTagMap_[tag];
screenSharedTagSet_.erase(pair);
viewsScreenSharedTagMap_.erase(tag);
#endif // NDEBUG
sharedTransitionAnimations_.erase(tag);
auto const &groupName = viewTagToSharedTag_[tag];
if (groupName.empty()) {
viewTagToSharedTag_.erase(tag);
return;
}
auto &group = sharedTransitionGroups_[groupName];
auto position = std::find(group.begin(), group.end(), tag);
if (position != group.end()) {
group.erase(position);
}
if (group.size() == 0) {
sharedTransitionGroups_.erase(groupName);
}
viewTagToSharedTag_.erase(tag);
ignoreProgressAnimationForTag_.erase(tag);
}
void LayoutAnimationsManager::clearLayoutAnimationConfig(const int tag) {
auto lock = std::unique_lock<std::recursive_mutex>(animationsMutex_);
enteringAnimations_.erase(tag);
exitingAnimations_.erase(tag);
layoutAnimations_.erase(tag);
shouldAnimateExitingForTag_.erase(tag);
clearSharedTransitionConfig(tag);
}
void LayoutAnimationsManager::startLayoutAnimation(
jsi::Runtime &rt,
const int tag,
const LayoutAnimationType type,
const jsi::Object &values) {
std::shared_ptr<Shareable> config, viewShareable;
{
auto lock = std::unique_lock<std::recursive_mutex>(animationsMutex_);
config = getConfigsForType(type)[tag];
}
// TODO: cache the following!!
jsi::Value layoutAnimationRepositoryAsValue =
rt.global()
.getPropertyAsObject(rt, "global")
.getProperty(rt, "LayoutAnimationsManager");
jsi::Function startAnimationForTag =
layoutAnimationRepositoryAsValue.getObject(rt).getPropertyAsFunction(
rt, "start");
startAnimationForTag.call(
rt,
jsi::Value(tag),
jsi::Value(static_cast<int>(type)),
values,
config->getJSValue(rt));
}
void LayoutAnimationsManager::cancelLayoutAnimation(
jsi::Runtime &rt,
const int tag) const {
jsi::Value layoutAnimationRepositoryAsValue =
rt.global()
.getPropertyAsObject(rt, "global")
.getProperty(rt, "LayoutAnimationsManager");
jsi::Function cancelLayoutAnimation =
layoutAnimationRepositoryAsValue.getObject(rt).getPropertyAsFunction(
rt, "stop");
cancelLayoutAnimation.call(rt, jsi::Value(tag));
}
/*
The top screen on the stack triggers the animation, so we need to find
the sibling view registered in the past. This method finds view
registered in the same transition group (with the same transition tag)
which has been added to that group directly before the one that we
provide as an argument.
*/
int LayoutAnimationsManager::findPrecedingViewTagForTransition(const int tag) {
auto const &groupName = viewTagToSharedTag_[tag];
auto const &group = sharedTransitionGroups_[groupName];
auto position = std::find(group.begin(), group.end(), tag);
if (position != group.end() && position != group.begin()) {
return *std::prev(position);
}
return -1;
}
#ifndef NDEBUG
std::string LayoutAnimationsManager::getScreenSharedTagPairString(
const int screenTag,
const std::string &sharedTag) const {
return std::to_string(screenTag) + ":" + sharedTag;
}
void LayoutAnimationsManager::checkDuplicateSharedTag(
const int viewTag,
const int screenTag) {
if (!viewTagToSharedTag_.count(viewTag)) {
return;
}
const auto &sharedTag = viewTagToSharedTag_[viewTag];
const auto &pair = getScreenSharedTagPairString(screenTag, sharedTag);
bool hasDuplicate = screenSharedTagSet_.count(pair);
if (hasDuplicate) {
jsLogger_->warnOnJS(
"[Reanimated] Duplicate shared tag \"" + sharedTag +
"\" on the same screen");
}
viewsScreenSharedTagMap_[viewTag] = pair;
screenSharedTagSet_.insert(pair);
}
#endif // NDEBUG
std::unordered_map<int, std::shared_ptr<Shareable>> &
LayoutAnimationsManager::getConfigsForType(const LayoutAnimationType type) {
switch (type) {
case ENTERING:
return enteringAnimations_;
case EXITING:
return exitingAnimations_;
case LAYOUT:
return layoutAnimations_;
case SHARED_ELEMENT_TRANSITION:
case SHARED_ELEMENT_TRANSITION_PROGRESS:
return sharedTransitionAnimations_;
default:
assert(false);
}
}
} // namespace reanimated

View File

@@ -0,0 +1,80 @@
#pragma once
#include "JSLogger.h"
#include "LayoutAnimationType.h"
#include "Shareables.h"
#include <jsi/jsi.h>
#include <stdio.h>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace reanimated {
using namespace facebook;
struct LayoutAnimationConfig {
int tag;
LayoutAnimationType type;
std::shared_ptr<Shareable> config;
std::string sharedTransitionTag;
};
class LayoutAnimationsManager {
public:
explicit LayoutAnimationsManager(const std::shared_ptr<JSLogger> &jsLogger)
: jsLogger_(jsLogger) {}
void configureAnimationBatch(
const std::vector<LayoutAnimationConfig> &layoutAnimationsBatch);
void setShouldAnimateExiting(const int tag, const bool value);
bool shouldAnimateExiting(const int tag, const bool shouldAnimate);
bool hasLayoutAnimation(const int tag, const LayoutAnimationType type);
void startLayoutAnimation(
jsi::Runtime &rt,
const int tag,
const LayoutAnimationType type,
const jsi::Object &values);
void clearLayoutAnimationConfig(const int tag);
void clearSharedTransitionConfig(const int tag);
void cancelLayoutAnimation(jsi::Runtime &rt, const int tag) const;
int findPrecedingViewTagForTransition(const int tag);
#ifndef NDEBUG
std::string getScreenSharedTagPairString(
const int screenTag,
const std::string &sharedTag) const;
void checkDuplicateSharedTag(const int viewTag, const int screenTag);
#endif
private:
std::unordered_map<int, std::shared_ptr<Shareable>> &getConfigsForType(
const LayoutAnimationType type);
std::shared_ptr<JSLogger> jsLogger_;
#ifndef NDEBUG
// This set's function is to detect duplicate sharedTags on a single screen
// it contains strings in form: "reactScreenTag:sharedTag"
std::unordered_set<std::string> screenSharedTagSet_;
// And this map is to remove collected pairs on SET removal
std::unordered_map<int, std::string> viewsScreenSharedTagMap_;
#endif
std::unordered_map<int, std::shared_ptr<Shareable>> enteringAnimations_;
std::unordered_map<int, std::shared_ptr<Shareable>> exitingAnimations_;
std::unordered_map<int, std::shared_ptr<Shareable>> layoutAnimations_;
std::unordered_map<int, std::shared_ptr<Shareable>>
sharedTransitionAnimations_;
std::unordered_set<int> ignoreProgressAnimationForTag_;
std::unordered_map<std::string, std::vector<int>> sharedTransitionGroups_;
std::unordered_map<int, std::string> viewTagToSharedTag_;
std::unordered_map<int, bool> shouldAnimateExitingForTag_;
mutable std::recursive_mutex
animationsMutex_; // Protects `enteringAnimations_`, `exitingAnimations_`,
// `layoutAnimations_`, `viewSharedValues_` and `shouldAnimateExitingForTag_`.
};
} // namespace reanimated

View File

@@ -0,0 +1,845 @@
#include "NativeReanimatedModule.h"
#ifdef RCT_NEW_ARCH_ENABLED
#include <react/renderer/uimanager/UIManagerBinding.h>
#include <react/renderer/uimanager/primitives.h>
#if REACT_NATIVE_MINOR_VERSION >= 73 && defined(RCT_NEW_ARCH_ENABLED)
#include <react/utils/CoreFeatures.h>
#endif
#endif
#include <functional>
#include <iomanip>
#include <memory>
#include <sstream>
#include <string>
#include <thread>
#include <unordered_map>
#ifdef RCT_NEW_ARCH_ENABLED
#include "ReanimatedCommitMarker.h"
#include "ShadowTreeCloner.h"
#endif
#include "AsyncQueue.h"
#include "CollectionUtils.h"
#include "EventHandlerRegistry.h"
#include "FeaturesConfig.h"
#include "JSScheduler.h"
#include "ReanimatedHiddenHeaders.h"
#include "Shareables.h"
#include "UIRuntimeDecorator.h"
#include "WorkletEventHandler.h"
#ifdef __ANDROID__
#include <fbjni/fbjni.h>
#endif
using namespace facebook;
#if REACT_NATIVE_MINOR_VERSION == 73 && defined(RCT_NEW_ARCH_ENABLED)
// Android can't find the definition of this static field
bool CoreFeatures::useNativeState;
#endif
namespace reanimated {
NativeReanimatedModule::NativeReanimatedModule(
jsi::Runtime &rnRuntime,
const std::shared_ptr<JSScheduler> &jsScheduler,
const std::shared_ptr<MessageQueueThread> &jsQueue,
const std::shared_ptr<UIScheduler> &uiScheduler,
const PlatformDepMethodsHolder &platformDepMethodsHolder,
const std::string &valueUnpackerCode,
const bool isBridgeless)
: NativeReanimatedModuleSpec(
isBridgeless ? nullptr : jsScheduler->getJSCallInvoker()),
isBridgeless_(isBridgeless),
jsQueue_(jsQueue),
jsScheduler_(jsScheduler),
uiScheduler_(uiScheduler),
uiWorkletRuntime_(std::make_shared<WorkletRuntime>(
rnRuntime,
jsQueue,
jsScheduler_,
"Reanimated UI runtime",
true /* supportsLocking */,
valueUnpackerCode)),
valueUnpackerCode_(valueUnpackerCode),
eventHandlerRegistry_(std::make_unique<EventHandlerRegistry>()),
requestRender_(platformDepMethodsHolder.requestRender),
onRenderCallback_([this](const double timestampMs) {
renderRequested_ = false;
onRender(timestampMs);
}),
animatedSensorModule_(platformDepMethodsHolder),
jsLogger_(std::make_shared<JSLogger>(jsScheduler_)),
layoutAnimationsManager_(jsLogger_),
#ifdef RCT_NEW_ARCH_ENABLED
synchronouslyUpdateUIPropsFunction_(
platformDepMethodsHolder.synchronouslyUpdateUIPropsFunction),
propsRegistry_(std::make_shared<PropsRegistry>()),
#else
obtainPropFunction_(platformDepMethodsHolder.obtainPropFunction),
configurePropsPlatformFunction_(
platformDepMethodsHolder.configurePropsFunction),
updatePropsFunction_(platformDepMethodsHolder.updatePropsFunction),
#endif
subscribeForKeyboardEventsFunction_(
platformDepMethodsHolder.subscribeForKeyboardEvents),
unsubscribeFromKeyboardEventsFunction_(
platformDepMethodsHolder.unsubscribeFromKeyboardEvents) {
commonInit(platformDepMethodsHolder);
}
void NativeReanimatedModule::commonInit(
const PlatformDepMethodsHolder &platformDepMethodsHolder) {
auto requestAnimationFrame =
[this](jsi::Runtime &rt, const jsi::Value &callback) {
this->requestAnimationFrame(rt, callback);
};
#ifdef RCT_NEW_ARCH_ENABLED
auto updateProps = [this](jsi::Runtime &rt, const jsi::Value &operations) {
this->updateProps(rt, operations);
};
auto removeFromPropsRegistry =
[this](jsi::Runtime &rt, const jsi::Value &viewTags) {
this->removeFromPropsRegistry(rt, viewTags);
};
auto measure = [this](jsi::Runtime &rt, const jsi::Value &shadowNodeValue) {
return this->measure(rt, shadowNodeValue);
};
auto dispatchCommand = [this](
jsi::Runtime &rt,
const jsi::Value &shadowNodeValue,
const jsi::Value &commandNameValue,
const jsi::Value &argsValue) {
this->dispatchCommand(rt, shadowNodeValue, commandNameValue, argsValue);
};
auto obtainProp = [this](
jsi::Runtime &rt,
const jsi::Value &shadowNodeWrapper,
const jsi::Value &propName) {
return this->obtainProp(rt, shadowNodeWrapper, propName);
};
#endif
jsi::Runtime &uiRuntime = uiWorkletRuntime_->getJSIRuntime();
UIRuntimeDecorator::decorate(
uiRuntime,
#ifdef RCT_NEW_ARCH_ENABLED
removeFromPropsRegistry,
obtainProp,
updateProps,
measure,
dispatchCommand,
#else
platformDepMethodsHolder.scrollToFunction,
platformDepMethodsHolder.obtainPropFunction,
platformDepMethodsHolder.updatePropsFunction,
platformDepMethodsHolder.measureFunction,
platformDepMethodsHolder.dispatchCommandFunction,
#endif
requestAnimationFrame,
platformDepMethodsHolder.getAnimationTimestamp,
platformDepMethodsHolder.setGestureStateFunction,
platformDepMethodsHolder.progressLayoutAnimation,
platformDepMethodsHolder.endLayoutAnimation,
platformDepMethodsHolder.maybeFlushUIUpdatesQueueFunction);
}
NativeReanimatedModule::~NativeReanimatedModule() {
// event handler registry and frame callbacks store some JSI values from UI
// runtime, so they have to go away before we tear down the runtime
eventHandlerRegistry_.reset();
frameCallbacks_.clear();
uiWorkletRuntime_.reset();
}
void NativeReanimatedModule::scheduleOnUI(
jsi::Runtime &rt,
const jsi::Value &worklet) {
auto shareableWorklet = extractShareableOrThrow<ShareableWorklet>(
rt, worklet, "[Reanimated] Only worklets can be scheduled to run on UI.");
uiScheduler_->scheduleOnUI([=] {
#if JS_RUNTIME_HERMES
// JSI's scope defined here allows for JSI-objects to be cleared up after
// each runtime loop. Within these loops we typically create some temporary
// JSI objects and hence it allows for such objects to be garbage collected
// much sooner.
// Apparently the scope API is only supported on Hermes at the moment.
const auto scope = jsi::Scope(uiWorkletRuntime_->getJSIRuntime());
#endif
uiWorkletRuntime_->runGuarded(shareableWorklet);
});
}
jsi::Value NativeReanimatedModule::executeOnUIRuntimeSync(
jsi::Runtime &rt,
const jsi::Value &worklet) {
return uiWorkletRuntime_->executeSync(rt, worklet);
}
jsi::Value NativeReanimatedModule::createWorkletRuntime(
jsi::Runtime &rt,
const jsi::Value &name,
const jsi::Value &initializer) {
auto workletRuntime = std::make_shared<WorkletRuntime>(
rt,
jsQueue_,
jsScheduler_,
name.asString(rt).utf8(rt),
false /* supportsLocking */,
valueUnpackerCode_);
auto initializerShareable = extractShareableOrThrow<ShareableWorklet>(
rt, initializer, "[Reanimated] Initializer must be a worklet.");
workletRuntime->runGuarded(initializerShareable);
return jsi::Object::createFromHostObject(rt, workletRuntime);
}
jsi::Value NativeReanimatedModule::scheduleOnRuntime(
jsi::Runtime &rt,
const jsi::Value &workletRuntimeValue,
const jsi::Value &shareableWorkletValue) {
reanimated::scheduleOnRuntime(rt, workletRuntimeValue, shareableWorkletValue);
return jsi::Value::undefined();
}
jsi::Value NativeReanimatedModule::makeShareableClone(
jsi::Runtime &rt,
const jsi::Value &value,
const jsi::Value &shouldRetainRemote,
const jsi::Value &nativeStateSource) {
return reanimated::makeShareableClone(
rt, value, shouldRetainRemote, nativeStateSource);
}
jsi::Value NativeReanimatedModule::registerEventHandler(
jsi::Runtime &rt,
const jsi::Value &worklet,
const jsi::Value &eventName,
const jsi::Value &emitterReactTag) {
static uint64_t NEXT_EVENT_HANDLER_ID = 1;
uint64_t newRegistrationId = NEXT_EVENT_HANDLER_ID++;
auto eventNameStr = eventName.asString(rt).utf8(rt);
auto handlerShareable = extractShareableOrThrow<ShareableWorklet>(
rt, worklet, "[Reanimated] Event handler must be a worklet.");
int emitterReactTagInt = emitterReactTag.asNumber();
uiScheduler_->scheduleOnUI([=] {
auto handler = std::make_shared<WorkletEventHandler>(
newRegistrationId, eventNameStr, emitterReactTagInt, handlerShareable);
eventHandlerRegistry_->registerEventHandler(std::move(handler));
});
return jsi::Value(static_cast<double>(newRegistrationId));
}
void NativeReanimatedModule::unregisterEventHandler(
jsi::Runtime &,
const jsi::Value &registrationId) {
uint64_t id = registrationId.asNumber();
uiScheduler_->scheduleOnUI(
[=] { eventHandlerRegistry_->unregisterEventHandler(id); });
}
#ifdef RCT_NEW_ARCH_ENABLED
static inline std::string intColorToHex(const int val) {
std::stringstream
invertedHexColorStream; // By default transparency is first, color second
invertedHexColorStream << std::setfill('0') << std::setw(8) << std::hex
<< val;
auto invertedHexColor = invertedHexColorStream.str();
auto hexColor =
"#" + invertedHexColor.substr(2, 6) + invertedHexColor.substr(0, 2);
return hexColor;
}
std::string NativeReanimatedModule::obtainPropFromShadowNode(
jsi::Runtime &rt,
const std::string &propName,
const ShadowNode::Shared &shadowNode) {
auto newestCloneOfShadowNode =
uiManager_->getNewestCloneOfShadowNode(*shadowNode);
if (propName == "width" || propName == "height" || propName == "top" ||
propName == "left") {
// These props are calculated from frame
auto layoutableShadowNode = dynamic_cast<LayoutableShadowNode const *>(
newestCloneOfShadowNode.get());
const auto &frame = layoutableShadowNode->layoutMetrics_.frame;
if (propName == "width") {
return std::to_string(frame.size.width);
} else if (propName == "height") {
return std::to_string(frame.size.height);
} else if (propName == "top") {
return std::to_string(frame.origin.y);
} else if (propName == "left") {
return std::to_string(frame.origin.x);
}
} else {
// These props are calculated from viewProps
auto props = newestCloneOfShadowNode->getProps();
auto viewProps = std::static_pointer_cast<const ViewProps>(props);
if (propName == "opacity") {
return std::to_string(viewProps->opacity);
} else if (propName == "zIndex") {
if (viewProps->zIndex.has_value()) {
return std::to_string(*viewProps->zIndex);
}
} else if (propName == "backgroundColor") {
return intColorToHex(*viewProps->backgroundColor);
}
}
throw std::runtime_error(std::string(
"Getting property `" + propName +
"` with function `getViewProp` is not supported"));
}
jsi::Value NativeReanimatedModule::getViewProp(
jsi::Runtime &rnRuntime,
const jsi::Value &shadowNodeWrapper,
const jsi::Value &propName,
const jsi::Value &callback) {
const auto propNameStr = propName.asString(rnRuntime).utf8(rnRuntime);
const auto funPtr = std::make_shared<jsi::Function>(
callback.getObject(rnRuntime).asFunction(rnRuntime));
const auto shadowNode = shadowNodeFromValue(rnRuntime, shadowNodeWrapper);
uiScheduler_->scheduleOnUI([=]() {
jsi::Runtime &uiRuntime = uiWorkletRuntime_->getJSIRuntime();
const auto resultStr =
obtainPropFromShadowNode(uiRuntime, propNameStr, shadowNode);
jsScheduler_->scheduleOnJS([=](jsi::Runtime &rnRuntime) {
const auto resultValue =
jsi::String::createFromUtf8(rnRuntime, resultStr);
funPtr->call(rnRuntime, resultValue);
});
});
return jsi::Value::undefined();
}
#else
jsi::Value NativeReanimatedModule::getViewProp(
jsi::Runtime &rnRuntime,
const jsi::Value &viewTag,
const jsi::Value &propName,
const jsi::Value &callback) {
const auto propNameStr = propName.asString(rnRuntime).utf8(rnRuntime);
const auto funPtr = std::make_shared<jsi::Function>(
callback.getObject(rnRuntime).asFunction(rnRuntime));
const int viewTagInt = viewTag.asNumber();
uiScheduler_->scheduleOnUI([=]() {
jsi::Runtime &uiRuntime = uiWorkletRuntime_->getJSIRuntime();
const jsi::Value propNameValue =
jsi::String::createFromUtf8(uiRuntime, propNameStr);
const auto resultValue =
obtainPropFunction_(uiRuntime, viewTagInt, propNameValue);
const auto resultStr = resultValue.asString(uiRuntime).utf8(uiRuntime);
jsScheduler_->scheduleOnJS([=](jsi::Runtime &rnRuntime) {
const auto resultValue =
jsi::String::createFromUtf8(rnRuntime, resultStr);
funPtr->call(rnRuntime, resultValue);
});
});
return jsi::Value::undefined();
}
#endif
jsi::Value NativeReanimatedModule::enableLayoutAnimations(
jsi::Runtime &,
const jsi::Value &config) {
FeaturesConfig::setLayoutAnimationEnabled(config.getBool());
return jsi::Value::undefined();
}
jsi::Value NativeReanimatedModule::configureProps(
jsi::Runtime &rt,
const jsi::Value &uiProps,
const jsi::Value &nativeProps) {
#ifdef RCT_NEW_ARCH_ENABLED
auto uiPropsArray = uiProps.asObject(rt).asArray(rt);
for (size_t i = 0; i < uiPropsArray.size(rt); ++i) {
auto name = uiPropsArray.getValueAtIndex(rt, i).asString(rt).utf8(rt);
animatablePropNames_.insert(name);
}
auto nativePropsArray = nativeProps.asObject(rt).asArray(rt);
for (size_t i = 0; i < nativePropsArray.size(rt); ++i) {
auto name = nativePropsArray.getValueAtIndex(rt, i).asString(rt).utf8(rt);
nativePropNames_.insert(name);
animatablePropNames_.insert(name);
}
#else
configurePropsPlatformFunction_(rt, uiProps, nativeProps);
#endif // RCT_NEW_ARCH_ENABLED
return jsi::Value::undefined();
}
jsi::Value NativeReanimatedModule::configureLayoutAnimationBatch(
jsi::Runtime &rt,
const jsi::Value &layoutAnimationsBatch) {
auto array = layoutAnimationsBatch.asObject(rt).asArray(rt);
size_t length = array.size(rt);
std::vector<LayoutAnimationConfig> batch(length);
for (int i = 0; i < length; i++) {
auto item = array.getValueAtIndex(rt, i).asObject(rt);
auto &batchItem = batch[i];
batchItem.tag = item.getProperty(rt, "viewTag").asNumber();
batchItem.type = static_cast<LayoutAnimationType>(
item.getProperty(rt, "type").asNumber());
auto config = item.getProperty(rt, "config");
if (config.isUndefined()) {
batchItem.config = nullptr;
} else {
batchItem.config = extractShareableOrThrow<ShareableObject>(
rt,
config,
"[Reanimated] Layout animation config must be an object.");
}
if (batch[i].type != SHARED_ELEMENT_TRANSITION &&
batch[i].type != SHARED_ELEMENT_TRANSITION_PROGRESS) {
continue;
}
auto sharedTransitionTag = item.getProperty(rt, "sharedTransitionTag");
if (sharedTransitionTag.isUndefined()) {
batch[i].config = nullptr;
} else {
batch[i].sharedTransitionTag = sharedTransitionTag.asString(rt).utf8(rt);
}
}
layoutAnimationsManager_.configureAnimationBatch(batch);
return jsi::Value::undefined();
}
void NativeReanimatedModule::setShouldAnimateExiting(
jsi::Runtime &rt,
const jsi::Value &viewTag,
const jsi::Value &shouldAnimate) {
layoutAnimationsManager_.setShouldAnimateExiting(
viewTag.asNumber(), shouldAnimate.getBool());
}
bool NativeReanimatedModule::isAnyHandlerWaitingForEvent(
const std::string &eventName,
const int emitterReactTag) {
return eventHandlerRegistry_->isAnyHandlerWaitingForEvent(
eventName, emitterReactTag);
}
void NativeReanimatedModule::requestAnimationFrame(
jsi::Runtime &rt,
const jsi::Value &callback) {
frameCallbacks_.push_back(std::make_shared<jsi::Value>(rt, callback));
maybeRequestRender();
}
void NativeReanimatedModule::maybeRequestRender() {
if (!renderRequested_) {
renderRequested_ = true;
jsi::Runtime &uiRuntime = uiWorkletRuntime_->getJSIRuntime();
requestRender_(onRenderCallback_, uiRuntime);
}
}
void NativeReanimatedModule::onRender(double timestampMs) {
auto callbacks = std::move(frameCallbacks_);
frameCallbacks_.clear();
jsi::Runtime &uiRuntime = uiWorkletRuntime_->getJSIRuntime();
jsi::Value timestamp{timestampMs};
for (const auto &callback : callbacks) {
runOnRuntimeGuarded(uiRuntime, *callback, timestamp);
}
}
jsi::Value NativeReanimatedModule::registerSensor(
jsi::Runtime &rt,
const jsi::Value &sensorType,
const jsi::Value &interval,
const jsi::Value &iosReferenceFrame,
const jsi::Value &sensorDataHandler) {
return animatedSensorModule_.registerSensor(
rt,
uiWorkletRuntime_,
sensorType,
interval,
iosReferenceFrame,
sensorDataHandler);
}
void NativeReanimatedModule::unregisterSensor(
jsi::Runtime &,
const jsi::Value &sensorId) {
animatedSensorModule_.unregisterSensor(sensorId);
}
void NativeReanimatedModule::cleanupSensors() {
animatedSensorModule_.unregisterAllSensors();
}
#ifdef RCT_NEW_ARCH_ENABLED
bool NativeReanimatedModule::isThereAnyLayoutProp(
jsi::Runtime &rt,
const jsi::Object &props) {
const jsi::Array propNames = props.getPropertyNames(rt);
for (size_t i = 0; i < propNames.size(rt); ++i) {
const std::string propName =
propNames.getValueAtIndex(rt, i).asString(rt).utf8(rt);
bool isLayoutProp =
nativePropNames_.find(propName) != nativePropNames_.end();
if (isLayoutProp) {
return true;
}
}
return false;
}
jsi::Value NativeReanimatedModule::filterNonAnimatableProps(
jsi::Runtime &rt,
const jsi::Value &props) {
jsi::Object nonAnimatableProps(rt);
bool hasAnyNonAnimatableProp = false;
const jsi::Object &propsObject = props.asObject(rt);
const jsi::Array &propNames = propsObject.getPropertyNames(rt);
for (size_t i = 0; i < propNames.size(rt); ++i) {
const std::string &propName =
propNames.getValueAtIndex(rt, i).asString(rt).utf8(rt);
if (!collection::contains(animatablePropNames_, propName)) {
hasAnyNonAnimatableProp = true;
const auto &propNameStr = propName.c_str();
const jsi::Value &propValue = propsObject.getProperty(rt, propNameStr);
nonAnimatableProps.setProperty(rt, propNameStr, propValue);
}
}
if (!hasAnyNonAnimatableProp) {
return jsi::Value::undefined();
}
return nonAnimatableProps;
}
#endif // RCT_NEW_ARCH_ENABLED
bool NativeReanimatedModule::handleEvent(
const std::string &eventName,
const int emitterReactTag,
const jsi::Value &payload,
double currentTime) {
eventHandlerRegistry_->processEvent(
uiWorkletRuntime_, currentTime, eventName, emitterReactTag, payload);
// TODO: return true if Reanimated successfully handled the event
// to avoid sending it to JavaScript
return false;
}
#ifdef RCT_NEW_ARCH_ENABLED
bool NativeReanimatedModule::handleRawEvent(
const RawEvent &rawEvent,
double currentTime) {
const EventTarget *eventTarget = rawEvent.eventTarget.get();
if (eventTarget == nullptr) {
// after app reload scrollView is unmounted and its content offset is set
// to 0 and view is thrown into recycle pool setting content offset
// triggers scroll event eventTarget is null though, because it's
// unmounting we can just ignore this event, because it's an event on
// unmounted component
return false;
}
int tag = eventTarget->getTag();
auto eventType = rawEvent.type;
if (eventType.rfind("top", 0) == 0) {
eventType = "on" + eventType.substr(3);
}
jsi::Runtime &rt = uiWorkletRuntime_->getJSIRuntime();
#if REACT_NATIVE_MINOR_VERSION >= 73
const auto &eventPayload = rawEvent.eventPayload;
jsi::Value payload = eventPayload->asJSIValue(rt);
#else
const auto &payloadFactory = rawEvent.payloadFactory;
jsi::Value payload = payloadFactory(rt);
#endif
auto res = handleEvent(eventType, tag, std::move(payload), currentTime);
// TODO: we should call performOperations conditionally if event is handled
// (res == true), but for now handleEvent always returns false. Thankfully,
// performOperations does not trigger a lot of code if there is nothing to
// be done so this is fine for now.
performOperations();
return res;
}
void NativeReanimatedModule::updateProps(
jsi::Runtime &rt,
const jsi::Value &operations) {
auto array = operations.asObject(rt).asArray(rt);
size_t length = array.size(rt);
for (size_t i = 0; i < length; ++i) {
auto item = array.getValueAtIndex(rt, i).asObject(rt);
auto shadowNodeWrapper = item.getProperty(rt, "shadowNodeWrapper");
auto shadowNode = shadowNodeFromValue(rt, shadowNodeWrapper);
const jsi::Value &updates = item.getProperty(rt, "updates");
operationsInBatch_.emplace_back(
shadowNode, std::make_unique<jsi::Value>(rt, updates));
// TODO: support multiple surfaces
surfaceId_ = shadowNode->getSurfaceId();
}
}
void NativeReanimatedModule::performOperations() {
if (operationsInBatch_.empty() && tagsToRemove_.empty()) {
// nothing to do
return;
}
auto copiedOperationsQueue = std::move(operationsInBatch_);
operationsInBatch_.clear();
jsi::Runtime &rt = uiWorkletRuntime_->getJSIRuntime();
{
auto lock = propsRegistry_->createLock();
// remove recently unmounted ShadowNodes from PropsRegistry
if (!tagsToRemove_.empty()) {
for (auto tag : tagsToRemove_) {
propsRegistry_->remove(tag);
}
tagsToRemove_.clear();
}
// Even if only non-layout props are changed, we need to store the update
// in PropsRegistry anyway so that React doesn't overwrite it in the next
// render. Currently, only opacity and transform are treated in a special
// way but backgroundColor, shadowOpacity etc. would get overwritten (see
// `_propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN`).
for (const auto &[shadowNode, props] : copiedOperationsQueue) {
propsRegistry_->update(shadowNode, dynamicFromValue(rt, *props));
}
}
for (const auto &[shadowNode, props] : copiedOperationsQueue) {
const jsi::Value &nonAnimatableProps = filterNonAnimatableProps(rt, *props);
if (nonAnimatableProps.isUndefined()) {
continue;
}
Tag viewTag = shadowNode->getTag();
jsi::Value maybeJSPropsUpdater =
rt.global().getProperty(rt, "updateJSProps");
assert(
maybeJSPropsUpdater.isObject() &&
"[Reanimated] `updateJSProps` not found");
jsi::Function jsPropsUpdater =
maybeJSPropsUpdater.asObject(rt).asFunction(rt);
jsPropsUpdater.call(rt, viewTag, nonAnimatableProps);
}
bool hasLayoutUpdates = false;
for (const auto &[shadowNode, props] : copiedOperationsQueue) {
if (isThereAnyLayoutProp(rt, props->asObject(rt))) {
hasLayoutUpdates = true;
break;
}
}
if (!hasLayoutUpdates) {
// If there's no layout props to be updated, we can apply the updates
// directly onto the components and skip the commit.
for (const auto &[shadowNode, props] : copiedOperationsQueue) {
Tag tag = shadowNode->getTag();
synchronouslyUpdateUIPropsFunction_(rt, tag, props->asObject(rt));
}
return;
}
if (propsRegistry_->shouldReanimatedSkipCommit()) {
// It may happen that `performOperations` is called on the UI thread
// while React Native tries to commit a new tree on the JS thread.
// In this case, we should skip the commit here and let React Native do
// it. The commit will include the current values from PropsRegistry which
// will be applied in ReanimatedCommitHook.
return;
}
react_native_assert(uiManager_ != nullptr);
const auto &shadowTreeRegistry = uiManager_->getShadowTreeRegistry();
shadowTreeRegistry.visit(surfaceId_, [&](ShadowTree const &shadowTree) {
// Mark the commit as Reanimated commit so that we can distinguish it
// in ReanimatedCommitHook.
ReanimatedCommitMarker commitMarker;
shadowTree.commit(
[&](RootShadowNode const &oldRootShadowNode)
-> RootShadowNode::Unshared {
auto rootNode =
oldRootShadowNode.ShadowNode::clone(ShadowNodeFragment{});
for (const auto &[shadowNode, props] : copiedOperationsQueue) {
const ShadowNodeFamily &family = shadowNode->getFamily();
react_native_assert(family.getSurfaceId() == surfaceId_);
#if REACT_NATIVE_MINOR_VERSION >= 73
// Fix for catching nullptr returned from commit hook was
// introduced in 0.72.4 but we have only check for minor version
// of React Native so enable that optimization in React Native >=
// 0.73
if (propsRegistry_->shouldReanimatedSkipCommit()) {
return nullptr;
}
#endif
auto newRootNode = cloneShadowTreeWithNewProps(
rootNode, family, RawProps(rt, *props));
if (newRootNode == nullptr) {
// this happens when React removed the component but Reanimated
// still tries to animate it, let's skip update for this
// specific component
continue;
}
rootNode = newRootNode;
}
auto newRoot = std::static_pointer_cast<RootShadowNode>(rootNode);
return newRoot;
},
{ /* .enableStateReconciliation = */
false,
#if REACT_NATIVE_MINOR_VERSION >= 72
/* .mountSynchronously = */ true,
#endif
/* .shouldYield = */ [this]() {
return propsRegistry_->shouldReanimatedSkipCommit();
}
});
});
}
void NativeReanimatedModule::removeFromPropsRegistry(
jsi::Runtime &rt,
const jsi::Value &viewTags) {
auto array = viewTags.asObject(rt).asArray(rt);
for (size_t i = 0, size = array.size(rt); i < size; ++i) {
tagsToRemove_.push_back(array.getValueAtIndex(rt, i).asNumber());
}
}
void NativeReanimatedModule::dispatchCommand(
jsi::Runtime &rt,
const jsi::Value &shadowNodeValue,
const jsi::Value &commandNameValue,
const jsi::Value &argsValue) {
ShadowNode::Shared shadowNode = shadowNodeFromValue(rt, shadowNodeValue);
std::string commandName = stringFromValue(rt, commandNameValue);
folly::dynamic args = commandArgsFromValue(rt, argsValue);
uiManager_->dispatchCommand(shadowNode, commandName, args);
}
jsi::String NativeReanimatedModule::obtainProp(
jsi::Runtime &rt,
const jsi::Value &shadowNodeWrapper,
const jsi::Value &propName) {
jsi::Runtime &uiRuntime = uiWorkletRuntime_->getJSIRuntime();
const auto propNameStr = propName.asString(rt).utf8(rt);
const auto shadowNode = shadowNodeFromValue(rt, shadowNodeWrapper);
const auto resultStr =
obtainPropFromShadowNode(uiRuntime, propNameStr, shadowNode);
return jsi::String::createFromUtf8(rt, resultStr);
}
jsi::Value NativeReanimatedModule::measure(
jsi::Runtime &rt,
const jsi::Value &shadowNodeValue) {
// based on implementation from UIManagerBinding.cpp
auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue);
auto layoutMetrics = uiManager_->getRelativeLayoutMetrics(
*shadowNode, nullptr, {/* .includeTransform = */ true});
if (layoutMetrics == EmptyLayoutMetrics) {
// Originally, in this case React Native returns `{0, 0, 0, 0, 0, 0}`,
// most likely due to the type of measure callback function which accepts
// just an array of numbers (not null). In Reanimated, `measure` returns
// `MeasuredDimensions | null`.
return jsi::Value::null();
}
auto newestCloneOfShadowNode =
uiManager_->getNewestCloneOfShadowNode(*shadowNode);
auto layoutableShadowNode =
dynamic_cast<LayoutableShadowNode const *>(newestCloneOfShadowNode.get());
facebook::react::Point originRelativeToParent =
layoutableShadowNode != nullptr
? layoutableShadowNode->getLayoutMetrics().frame.origin
: facebook::react::Point();
auto frame = layoutMetrics.frame;
jsi::Object result(rt);
result.setProperty(
rt, "x", jsi::Value(static_cast<double>(originRelativeToParent.x)));
result.setProperty(
rt, "y", jsi::Value(static_cast<double>(originRelativeToParent.y)));
result.setProperty(
rt, "width", jsi::Value(static_cast<double>(frame.size.width)));
result.setProperty(
rt, "height", jsi::Value(static_cast<double>(frame.size.height)));
result.setProperty(
rt, "pageX", jsi::Value(static_cast<double>(frame.origin.x)));
result.setProperty(
rt, "pageY", jsi::Value(static_cast<double>(frame.origin.y)));
return result;
}
void NativeReanimatedModule::initializeFabric(
const std::shared_ptr<UIManager> &uiManager) {
uiManager_ = uiManager;
commitHook_ =
std::make_shared<ReanimatedCommitHook>(propsRegistry_, uiManager_);
#if REACT_NATIVE_MINOR_VERSION >= 73
mountHook_ =
std::make_shared<ReanimatedMountHook>(propsRegistry_, uiManager_);
#endif
}
#endif // RCT_NEW_ARCH_ENABLED
jsi::Value NativeReanimatedModule::subscribeForKeyboardEvents(
jsi::Runtime &rt,
const jsi::Value &handlerWorklet,
const jsi::Value &isStatusBarTranslucent) {
auto shareableHandler = extractShareableOrThrow<ShareableWorklet>(
rt,
handlerWorklet,
"[Reanimated] Keyboard event handler must be a worklet.");
return subscribeForKeyboardEventsFunction_(
[=](int keyboardState, int height) {
uiWorkletRuntime_->runGuarded(
shareableHandler, jsi::Value(keyboardState), jsi::Value(height));
},
isStatusBarTranslucent.getBool());
}
void NativeReanimatedModule::unsubscribeFromKeyboardEvents(
jsi::Runtime &,
const jsi::Value &listenerId) {
unsubscribeFromKeyboardEventsFunction_(listenerId.asNumber());
}
} // namespace reanimated

View File

@@ -0,0 +1,239 @@
#pragma once
#ifdef RCT_NEW_ARCH_ENABLED
#include <react/renderer/uimanager/UIManager.h>
#endif
#include <memory>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "AnimatedSensorModule.h"
#include "EventHandlerRegistry.h"
#include "JSScheduler.h"
#include "LayoutAnimationsManager.h"
#include "NativeReanimatedModuleSpec.h"
#include "PlatformDepMethodsHolder.h"
#include "SingleInstanceChecker.h"
#include "UIScheduler.h"
#ifdef RCT_NEW_ARCH_ENABLED
#include "PropsRegistry.h"
#include "ReanimatedCommitHook.h"
#if REACT_NATIVE_MINOR_VERSION >= 73
#include "ReanimatedMountHook.h"
#endif
#endif
namespace reanimated {
class NativeReanimatedModule : public NativeReanimatedModuleSpec {
public:
NativeReanimatedModule(
jsi::Runtime &rnRuntime,
const std::shared_ptr<JSScheduler> &jsScheduler,
const std::shared_ptr<MessageQueueThread> &jsQueue,
const std::shared_ptr<UIScheduler> &uiScheduler,
const PlatformDepMethodsHolder &platformDepMethodsHolder,
const std::string &valueUnpackerCode,
const bool isBridgeless);
~NativeReanimatedModule();
jsi::Value makeShareableClone(
jsi::Runtime &rt,
const jsi::Value &value,
const jsi::Value &shouldRetainRemote,
const jsi::Value &nativeStateSource) override;
void scheduleOnUI(jsi::Runtime &rt, const jsi::Value &worklet) override;
jsi::Value executeOnUIRuntimeSync(jsi::Runtime &rt, const jsi::Value &worklet)
override;
jsi::Value createWorkletRuntime(
jsi::Runtime &rt,
const jsi::Value &name,
const jsi::Value &initializer) override;
jsi::Value scheduleOnRuntime(
jsi::Runtime &rt,
const jsi::Value &workletRuntimeValue,
const jsi::Value &shareableWorkletValue) override;
jsi::Value registerEventHandler(
jsi::Runtime &rt,
const jsi::Value &worklet,
const jsi::Value &eventName,
const jsi::Value &emitterReactTag) override;
void unregisterEventHandler(
jsi::Runtime &rt,
const jsi::Value &registrationId) override;
jsi::Value getViewProp(
jsi::Runtime &rt,
#ifdef RCT_NEW_ARCH_ENABLED
const jsi::Value &shadowNodeWrapper,
#else
const jsi::Value &viewTag,
#endif
const jsi::Value &propName,
const jsi::Value &callback) override;
jsi::Value enableLayoutAnimations(jsi::Runtime &rt, const jsi::Value &config)
override;
jsi::Value configureProps(
jsi::Runtime &rt,
const jsi::Value &uiProps,
const jsi::Value &nativeProps) override;
jsi::Value configureLayoutAnimationBatch(
jsi::Runtime &rt,
const jsi::Value &layoutAnimationsBatch) override;
void setShouldAnimateExiting(
jsi::Runtime &rt,
const jsi::Value &viewTag,
const jsi::Value &shouldAnimate) override;
void onRender(double timestampMs);
bool isAnyHandlerWaitingForEvent(
const std::string &eventName,
const int emitterReactTag);
void maybeRequestRender();
bool handleEvent(
const std::string &eventName,
const int emitterReactTag,
const jsi::Value &payload,
double currentTime);
inline std::shared_ptr<JSLogger> getJSLogger() const {
return jsLogger_;
}
#ifdef RCT_NEW_ARCH_ENABLED
bool handleRawEvent(const RawEvent &rawEvent, double currentTime);
void updateProps(jsi::Runtime &rt, const jsi::Value &operations);
void removeFromPropsRegistry(jsi::Runtime &rt, const jsi::Value &viewTags);
void performOperations();
void dispatchCommand(
jsi::Runtime &rt,
const jsi::Value &shadowNodeValue,
const jsi::Value &commandNameValue,
const jsi::Value &argsValue);
jsi::String obtainProp(
jsi::Runtime &rt,
const jsi::Value &shadowNodeWrapper,
const jsi::Value &propName);
jsi::Value measure(jsi::Runtime &rt, const jsi::Value &shadowNodeValue);
void initializeFabric(const std::shared_ptr<UIManager> &uiManager);
std::string obtainPropFromShadowNode(
jsi::Runtime &rt,
const std::string &propName,
const ShadowNode::Shared &shadowNode);
#endif
jsi::Value registerSensor(
jsi::Runtime &rt,
const jsi::Value &sensorType,
const jsi::Value &interval,
const jsi::Value &iosReferenceFrame,
const jsi::Value &sensorDataContainer) override;
void unregisterSensor(jsi::Runtime &rt, const jsi::Value &sensorId) override;
void cleanupSensors();
jsi::Value subscribeForKeyboardEvents(
jsi::Runtime &rt,
const jsi::Value &keyboardEventContainer,
const jsi::Value &isStatusBarTranslucent) override;
void unsubscribeFromKeyboardEvents(
jsi::Runtime &rt,
const jsi::Value &listenerId) override;
inline LayoutAnimationsManager &layoutAnimationsManager() {
return layoutAnimationsManager_;
}
inline jsi::Runtime &getUIRuntime() {
return uiWorkletRuntime_->getJSIRuntime();
}
inline bool isBridgeless() const {
return isBridgeless_;
}
private:
void commonInit(const PlatformDepMethodsHolder &platformDepMethodsHolder);
void requestAnimationFrame(jsi::Runtime &rt, const jsi::Value &callback);
#ifdef RCT_NEW_ARCH_ENABLED
bool isThereAnyLayoutProp(jsi::Runtime &rt, const jsi::Object &props);
jsi::Value filterNonAnimatableProps(
jsi::Runtime &rt,
const jsi::Value &props);
#endif // RCT_NEW_ARCH_ENABLED
const bool isBridgeless_;
const std::shared_ptr<MessageQueueThread> jsQueue_;
const std::shared_ptr<JSScheduler> jsScheduler_;
const std::shared_ptr<UIScheduler> uiScheduler_;
std::shared_ptr<WorkletRuntime> uiWorkletRuntime_;
std::string valueUnpackerCode_;
std::unique_ptr<EventHandlerRegistry> eventHandlerRegistry_;
const RequestRenderFunction requestRender_;
std::vector<std::shared_ptr<jsi::Value>> frameCallbacks_;
volatile bool renderRequested_{false};
const std::function<void(const double)> onRenderCallback_;
AnimatedSensorModule animatedSensorModule_;
const std::shared_ptr<JSLogger> jsLogger_;
LayoutAnimationsManager layoutAnimationsManager_;
#ifdef RCT_NEW_ARCH_ENABLED
const SynchronouslyUpdateUIPropsFunction synchronouslyUpdateUIPropsFunction_;
std::unordered_set<std::string> nativePropNames_; // filled by configureProps
std::unordered_set<std::string>
animatablePropNames_; // filled by configureProps
std::shared_ptr<UIManager> uiManager_;
// After app reload, surfaceId on iOS is still 1 but on Android it's 11.
// We can store surfaceId of the most recent ShadowNode as a workaround.
SurfaceId surfaceId_ = -1;
std::vector<std::pair<ShadowNode::Shared, std::unique_ptr<jsi::Value>>>
operationsInBatch_; // TODO: refactor std::pair to custom struct
std::shared_ptr<PropsRegistry> propsRegistry_;
std::shared_ptr<ReanimatedCommitHook> commitHook_;
#if REACT_NATIVE_MINOR_VERSION >= 73
std::shared_ptr<ReanimatedMountHook> mountHook_;
#endif
std::vector<Tag> tagsToRemove_; // from `propsRegistry_`
#else
const ObtainPropFunction obtainPropFunction_;
const ConfigurePropsFunction configurePropsPlatformFunction_;
const UpdatePropsFunction updatePropsFunction_;
#endif
const KeyboardEventSubscribeFunction subscribeForKeyboardEventsFunction_;
const KeyboardEventUnsubscribeFunction unsubscribeFromKeyboardEventsFunction_;
#ifndef NDEBUG
SingleInstanceChecker<NativeReanimatedModule> singleInstanceChecker_;
#endif
};
} // namespace reanimated

View File

@@ -0,0 +1,209 @@
#include "NativeReanimatedModuleSpec.h"
#include <utility>
#define SPEC_PREFIX(FN_NAME) __hostFunction_NativeReanimatedModuleSpec_##FN_NAME
namespace reanimated {
// SharedValue
static jsi::Value SPEC_PREFIX(makeShareableClone)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
return static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->makeShareableClone(
rt, std::move(args[0]), std::move(args[1]), std::move(args[2]));
}
// scheduler
static jsi::Value SPEC_PREFIX(scheduleOnUI)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->scheduleOnUI(rt, std::move(args[0]));
return jsi::Value::undefined();
}
static jsi::Value SPEC_PREFIX(executeOnUIRuntimeSync)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
return static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->executeOnUIRuntimeSync(rt, std::move(args[0]));
}
static jsi::Value SPEC_PREFIX(createWorkletRuntime)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
return static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->createWorkletRuntime(rt, std::move(args[0]), std::move(args[1]));
}
static jsi::Value SPEC_PREFIX(scheduleOnRuntime)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
return static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->scheduleOnRuntime(rt, std::move(args[0]), std::move(args[1]));
}
static jsi::Value SPEC_PREFIX(registerEventHandler)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
return static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->registerEventHandler(
rt, std::move(args[0]), std::move(args[1]), std::move(args[2]));
}
static jsi::Value SPEC_PREFIX(unregisterEventHandler)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->unregisterEventHandler(rt, std::move(args[0]));
return jsi::Value::undefined();
}
static jsi::Value SPEC_PREFIX(getViewProp)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->getViewProp(
rt, std::move(args[0]), std::move(args[1]), std::move(args[2]));
return jsi::Value::undefined();
}
static jsi::Value SPEC_PREFIX(enableLayoutAnimations)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->enableLayoutAnimations(rt, std::move(args[0]));
return jsi::Value::undefined();
}
static jsi::Value SPEC_PREFIX(registerSensor)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
return static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->registerSensor(
rt,
std::move(args[0]),
std::move(args[1]),
std::move(args[2]),
std::move(args[3]));
}
static jsi::Value SPEC_PREFIX(unregisterSensor)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->unregisterSensor(rt, std::move(args[0]));
return jsi::Value::undefined();
}
static jsi::Value SPEC_PREFIX(configureProps)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->configureProps(rt, std::move(args[0]), std::move(args[1]));
return jsi::Value::undefined();
}
static jsi::Value SPEC_PREFIX(subscribeForKeyboardEvents)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
return static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->subscribeForKeyboardEvents(rt, std::move(args[0]), std::move(args[1]));
}
static jsi::Value SPEC_PREFIX(unsubscribeFromKeyboardEvents)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->unsubscribeFromKeyboardEvents(rt, std::move(args[0]));
return jsi::Value::undefined();
}
static jsi::Value SPEC_PREFIX(configureLayoutAnimationBatch)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
return static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->configureLayoutAnimationBatch(rt, std::move(args[0]));
}
static jsi::Value SPEC_PREFIX(setShouldAnimateExiting)(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t) {
static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->setShouldAnimateExiting(rt, std::move(args[0]), std::move(args[1]));
return jsi::Value::undefined();
}
NativeReanimatedModuleSpec::NativeReanimatedModuleSpec(
const std::shared_ptr<CallInvoker> &jsInvoker)
: TurboModule("NativeReanimated", jsInvoker) {
methodMap_["makeShareableClone"] =
MethodMetadata{2, SPEC_PREFIX(makeShareableClone)};
methodMap_["scheduleOnUI"] = MethodMetadata{1, SPEC_PREFIX(scheduleOnUI)};
methodMap_["executeOnUIRuntimeSync"] =
MethodMetadata{1, SPEC_PREFIX(executeOnUIRuntimeSync)};
methodMap_["createWorkletRuntime"] =
MethodMetadata{2, SPEC_PREFIX(createWorkletRuntime)};
methodMap_["scheduleOnRuntime"] =
MethodMetadata{2, SPEC_PREFIX(scheduleOnRuntime)};
methodMap_["registerEventHandler"] =
MethodMetadata{3, SPEC_PREFIX(registerEventHandler)};
methodMap_["unregisterEventHandler"] =
MethodMetadata{1, SPEC_PREFIX(unregisterEventHandler)};
methodMap_["getViewProp"] = MethodMetadata{3, SPEC_PREFIX(getViewProp)};
methodMap_["enableLayoutAnimations"] =
MethodMetadata{2, SPEC_PREFIX(enableLayoutAnimations)};
methodMap_["registerSensor"] = MethodMetadata{4, SPEC_PREFIX(registerSensor)};
methodMap_["unregisterSensor"] =
MethodMetadata{1, SPEC_PREFIX(unregisterSensor)};
methodMap_["configureProps"] = MethodMetadata{2, SPEC_PREFIX(configureProps)};
methodMap_["subscribeForKeyboardEvents"] =
MethodMetadata{2, SPEC_PREFIX(subscribeForKeyboardEvents)};
methodMap_["unsubscribeFromKeyboardEvents"] =
MethodMetadata{1, SPEC_PREFIX(unsubscribeFromKeyboardEvents)};
methodMap_["configureLayoutAnimationBatch"] =
MethodMetadata{1, SPEC_PREFIX(configureLayoutAnimationBatch)};
methodMap_["setShouldAnimateExitingForTag"] =
MethodMetadata{2, SPEC_PREFIX(setShouldAnimateExiting)};
}
} // namespace reanimated

View File

@@ -0,0 +1,110 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#ifdef ANDROID
#include "TurboModule.h"
#else
#include <ReactCommon/TurboModule.h>
#endif
#include <ReactCommon/CallInvoker.h>
using namespace facebook;
using namespace react;
namespace reanimated {
class JSI_EXPORT NativeReanimatedModuleSpec : public TurboModule {
protected:
explicit NativeReanimatedModuleSpec(
const std::shared_ptr<CallInvoker> &jsInvoker);
public:
// SharedValue
virtual jsi::Value makeShareableClone(
jsi::Runtime &rt,
const jsi::Value &value,
const jsi::Value &shouldRetainRemote,
const jsi::Value &nativeStateSource) = 0;
// Scheduling
virtual void scheduleOnUI(jsi::Runtime &rt, const jsi::Value &worklet) = 0;
virtual jsi::Value executeOnUIRuntimeSync(
jsi::Runtime &rt,
const jsi::Value &worklet) = 0;
// Worklet runtime
virtual jsi::Value createWorkletRuntime(
jsi::Runtime &rt,
const jsi::Value &name,
const jsi::Value &initializer) = 0;
virtual jsi::Value scheduleOnRuntime(
jsi::Runtime &rt,
const jsi::Value &workletRuntimeValue,
const jsi::Value &shareableWorkletValue) = 0;
// events
virtual jsi::Value registerEventHandler(
jsi::Runtime &rt,
const jsi::Value &worklet,
const jsi::Value &eventName,
const jsi::Value &emitterReactTag) = 0;
virtual void unregisterEventHandler(
jsi::Runtime &rt,
const jsi::Value &registrationId) = 0;
// views
virtual jsi::Value getViewProp(
jsi::Runtime &rt,
#ifdef RCT_NEW_ARCH_ENABLED
const jsi::Value &shadowNodeWrapper,
#else
const jsi::Value &viewTag,
#endif
const jsi::Value &propName,
const jsi::Value &callback) = 0;
// sensors
virtual jsi::Value registerSensor(
jsi::Runtime &rt,
const jsi::Value &sensorType,
const jsi::Value &interval,
const jsi::Value &iosReferenceFrame,
const jsi::Value &sensorDataContainer) = 0;
virtual void unregisterSensor(
jsi::Runtime &rt,
const jsi::Value &sensorId) = 0;
// keyboard
virtual jsi::Value subscribeForKeyboardEvents(
jsi::Runtime &rt,
const jsi::Value &keyboardEventContainer,
const jsi::Value &isStatusBarTranslucent) = 0;
virtual void unsubscribeFromKeyboardEvents(
jsi::Runtime &rt,
const jsi::Value &listenerId) = 0;
// other
virtual jsi::Value enableLayoutAnimations(
jsi::Runtime &rt,
const jsi::Value &config) = 0;
virtual jsi::Value configureProps(
jsi::Runtime &rt,
const jsi::Value &uiProps,
const jsi::Value &nativeProps) = 0;
// layout animations
virtual jsi::Value configureLayoutAnimationBatch(
jsi::Runtime &rt,
const jsi::Value &layoutAnimationsBatch) = 0;
virtual void setShouldAnimateExiting(
jsi::Runtime &rt,
const jsi::Value &viewTag,
const jsi::Value &shouldAnimate) = 0;
};
} // namespace reanimated

View File

@@ -0,0 +1,49 @@
#include "RNRuntimeDecorator.h"
#include "ReanimatedVersion.h"
namespace reanimated {
void RNRuntimeDecorator::decorate(
jsi::Runtime &rnRuntime,
const std::shared_ptr<NativeReanimatedModule> &nativeReanimatedModule,
const bool isReducedMotion) {
rnRuntime.global().setProperty(rnRuntime, "_WORKLET", false);
jsi::Runtime &uiRuntime = nativeReanimatedModule->getUIRuntime();
auto workletRuntimeValue =
rnRuntime.global()
.getPropertyAsObject(rnRuntime, "ArrayBuffer")
.asFunction(rnRuntime)
.callAsConstructor(rnRuntime, {static_cast<double>(sizeof(void *))});
uintptr_t *workletRuntimeData = reinterpret_cast<uintptr_t *>(
workletRuntimeValue.getObject(rnRuntime).getArrayBuffer(rnRuntime).data(
rnRuntime));
workletRuntimeData[0] = reinterpret_cast<uintptr_t>(&uiRuntime);
rnRuntime.global().setProperty(
rnRuntime, "_WORKLET_RUNTIME", workletRuntimeValue);
#ifdef RCT_NEW_ARCH_ENABLED
constexpr auto isFabric = true;
#else
constexpr auto isFabric = false;
#endif // RCT_NEW_ARCH_ENABLED
rnRuntime.global().setProperty(rnRuntime, "_IS_FABRIC", isFabric);
rnRuntime.global().setProperty(
rnRuntime, "_IS_BRIDGELESS", nativeReanimatedModule->isBridgeless());
#ifndef NDEBUG
checkJSVersion(rnRuntime, nativeReanimatedModule->getJSLogger());
#endif // NDEBUG
injectReanimatedCppVersion(rnRuntime);
rnRuntime.global().setProperty(
rnRuntime, "_REANIMATED_IS_REDUCED_MOTION", isReducedMotion);
rnRuntime.global().setProperty(
rnRuntime,
jsi::PropNameID::forAscii(rnRuntime, "__reanimatedModuleProxy"),
jsi::Object::createFromHostObject(rnRuntime, nativeReanimatedModule));
}
} // namespace reanimated

View File

@@ -0,0 +1,21 @@
#pragma once
#include <jsi/jsi.h>
#include <memory>
#include "NativeReanimatedModule.h"
using namespace facebook;
namespace reanimated {
class RNRuntimeDecorator {
public:
static void decorate(
jsi::Runtime &rnRuntime,
const std::shared_ptr<NativeReanimatedModule> &nativeReanimatedModule,
const bool isReducedMotion);
};
} // namespace reanimated

View File

@@ -0,0 +1,140 @@
#include "ReanimatedHermesRuntime.h"
// Only include this file in Hermes-enabled builds as some platforms (like tvOS)
// don't support hermes and it causes the compilation to fail.
#if JS_RUNTIME_HERMES
#include <cxxreact/MessageQueueThread.h>
#include <jsi/decorator.h>
#include <jsi/jsi.h>
#include <memory>
#include <string>
#include <utility>
#if __has_include(<reacthermes/HermesExecutorFactory.h>)
#include <reacthermes/HermesExecutorFactory.h>
#else // __has_include(<hermes/hermes.h>) || ANDROID
#include <hermes/hermes.h>
#endif
namespace reanimated {
using namespace facebook;
using namespace react;
#if HERMES_ENABLE_DEBUGGER
#if REACT_NATIVE_MINOR_VERSION >= 73
using namespace facebook::hermes::inspector_modern;
#else
using namespace facebook::hermes::inspector;
#endif
#endif // HERMES_ENABLE_DEBUGGER
#if HERMES_ENABLE_DEBUGGER
class HermesExecutorRuntimeAdapter : public RuntimeAdapter {
public:
explicit HermesExecutorRuntimeAdapter(
facebook::hermes::HermesRuntime &hermesRuntime,
const std::shared_ptr<MessageQueueThread> &thread)
: hermesRuntime_(hermesRuntime), thread_(std::move(thread)) {}
virtual ~HermesExecutorRuntimeAdapter() {
// This is required by iOS, because there is an assertion in the destructor
// that the thread was indeed `quit` before
thread_->quitSynchronous();
}
#if REACT_NATIVE_MINOR_VERSION >= 71
facebook::hermes::HermesRuntime &getRuntime() override {
return hermesRuntime_;
}
#else
facebook::jsi::Runtime &getRuntime() override {
return hermesRuntime_;
}
facebook::hermes::debugger::Debugger &getDebugger() override {
return hermesRuntime_.getDebugger();
}
#endif // REACT_NATIVE_MINOR_VERSION
// This is not empty in the original implementation, but we decided to tickle
// the runtime by running a small piece of code on every frame as using this
// required us to hold a refernce to the runtime inside this adapter which
// caused issues while reloading the app.
void tickleJs() override {}
public:
facebook::hermes::HermesRuntime &hermesRuntime_;
std::shared_ptr<MessageQueueThread> thread_;
};
#endif // HERMES_ENABLE_DEBUGGER
ReanimatedHermesRuntime::ReanimatedHermesRuntime(
std::unique_ptr<facebook::hermes::HermesRuntime> runtime,
const std::shared_ptr<MessageQueueThread> &jsQueue,
const std::string &name)
: jsi::WithRuntimeDecorator<ReanimatedReentrancyCheck>(
*runtime,
reentrancyCheck_),
runtime_(std::move(runtime)) {
#if HERMES_ENABLE_DEBUGGER
auto adapter =
std::make_unique<HermesExecutorRuntimeAdapter>(*runtime_, jsQueue);
#if REACT_NATIVE_MINOR_VERSION >= 71
debugToken_ = chrome::enableDebugging(std::move(adapter), name);
#else
chrome::enableDebugging(std::move(adapter), name);
#endif // REACT_NATIVE_MINOR_VERSION
#else
// This is required by iOS, because there is an assertion in the destructor
// that the thread was indeed `quit` before
jsQueue->quitSynchronous();
#endif // HERMES_ENABLE_DEBUGGER
#ifndef NDEBUG
facebook::hermes::HermesRuntime *wrappedRuntime = runtime_.get();
jsi::Value evalWithSourceMap = jsi::Function::createFromHostFunction(
*runtime_,
jsi::PropNameID::forAscii(*runtime_, "evalWithSourceMap"),
3,
[wrappedRuntime](
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count) -> jsi::Value {
auto code = std::make_shared<const jsi::StringBuffer>(
args[0].asString(rt).utf8(rt));
std::string sourceURL;
if (count > 1 && args[1].isString()) {
sourceURL = args[1].asString(rt).utf8(rt);
}
std::shared_ptr<const jsi::Buffer> sourceMap;
if (count > 2 && args[2].isString()) {
sourceMap = std::make_shared<const jsi::StringBuffer>(
args[2].asString(rt).utf8(rt));
}
return wrappedRuntime->evaluateJavaScriptWithSourceMap(
code, sourceMap, sourceURL);
});
runtime_->global().setProperty(
*runtime_, "evalWithSourceMap", evalWithSourceMap);
#endif // NDEBUG
}
ReanimatedHermesRuntime::~ReanimatedHermesRuntime() {
#if HERMES_ENABLE_DEBUGGER
// We have to disable debugging before the runtime is destroyed.
#if REACT_NATIVE_MINOR_VERSION >= 71
chrome::disableDebugging(debugToken_);
#else
chrome::disableDebugging(*runtime_);
#endif // REACT_NATIVE_MINOR_VERSION
#endif // HERMES_ENABLE_DEBUGGER
}
} // namespace reanimated
#endif // JS_RUNTIME_HERMES

View File

@@ -0,0 +1,149 @@
#pragma once
// JS_RUNTIME_HERMES is only set on Android so we have to check __has_include
// on iOS.
#if __APPLE__ && \
(__has_include( \
<reacthermes/HermesExecutorFactory.h>) || __has_include(<hermes/hermes.h>))
#define JS_RUNTIME_HERMES 1
#endif
// Only include this file in Hermes-enabled builds as some platforms (like tvOS)
// don't support hermes and it causes the compilation to fail.
#if JS_RUNTIME_HERMES
#include <cxxreact/MessageQueueThread.h>
#include <jsi/decorator.h>
#include <jsi/jsi.h>
#include <atomic>
#include <memory>
#include <string>
#include <thread>
#if __has_include(<reacthermes/HermesExecutorFactory.h>)
#include <reacthermes/HermesExecutorFactory.h>
#else // __has_include(<hermes/hermes.h>) || ANDROID
#include <hermes/hermes.h>
#endif
#if HERMES_ENABLE_DEBUGGER
#if REACT_NATIVE_MINOR_VERSION >= 73
#include <hermes/inspector-modern/chrome/Registration.h>
#else
#include <hermes/inspector/RuntimeAdapter.h>
#include <hermes/inspector/chrome/Registration.h>
#endif
#endif // HERMES_ENABLE_DEBUGGER
namespace reanimated {
using namespace facebook;
using namespace react;
#if HERMES_ENABLE_DEBUGGER
#if REACT_NATIVE_MINOR_VERSION >= 73
using namespace facebook::hermes::inspector_modern;
#else
using namespace facebook::hermes::inspector;
#endif
#endif // HERMES_ENABLE_DEBUGGER
// ReentrancyCheck is copied from React Native
// from ReactCommon/hermes/executor/HermesExecutorFactory.cpp
// https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.cpp
struct ReanimatedReentrancyCheck {
// This is effectively a very subtle and complex assert, so only
// include it in builds which would include asserts.
#ifndef NDEBUG
ReanimatedReentrancyCheck() : tid(std::thread::id()), depth(0) {}
void before() {
std::thread::id this_id = std::this_thread::get_id();
std::thread::id expected = std::thread::id();
// A note on memory ordering: the main purpose of these checks is
// to observe a before/before race, without an intervening after.
// This will be detected by the compare_exchange_strong atomicity
// properties, regardless of memory order.
//
// For everything else, it is easiest to think of 'depth' as a
// proxy for any access made inside the VM. If access to depth
// are reordered incorrectly, the same could be true of any other
// operation made by the VM. In fact, using acquire/release
// memory ordering could create barriers which mask a programmer
// error. So, we use relaxed memory order, to avoid masking
// actual ordering errors. Although, in practice, ordering errors
// of this sort would be surprising, because the decorator would
// need to call after() without before().
if (tid.compare_exchange_strong(
expected, this_id, std::memory_order_relaxed)) {
// Returns true if tid and expected were the same. If they
// were, then the stored tid referred to no thread, and we
// atomically saved this thread's tid. Now increment depth.
assert(depth == 0 && "[Reanimated] No thread id, but depth != 0");
++depth;
} else if (expected == this_id) {
// If the stored tid referred to a thread, expected was set to
// that value. If that value is this thread's tid, that's ok,
// just increment depth again.
assert(depth != 0 && "[Reanimated] Thread id was set, but depth == 0");
++depth;
} else {
// The stored tid was some other thread. This indicates a bad
// programmer error, where VM methods were called on two
// different threads unsafely. Fail fast (and hard) so the
// crash can be analyzed.
__builtin_trap();
}
}
void after() {
assert(
tid.load(std::memory_order_relaxed) == std::this_thread::get_id() &&
"[Reanimated] No thread id in after()");
if (--depth == 0) {
// If we decremented depth to zero, store no-thread into tid.
std::thread::id expected = std::this_thread::get_id();
bool didWrite = tid.compare_exchange_strong(
expected, std::thread::id(), std::memory_order_relaxed);
assert(didWrite && "[Reanimated] Decremented to zero, but no tid write");
}
}
std::atomic<std::thread::id> tid;
// This is not atomic, as it is only written or read from the owning
// thread.
unsigned int depth;
#endif // NDEBUG
};
// This is in fact a subclass of jsi::Runtime! WithRuntimeDecorator is a
// template class that is a subclass of DecoratedRuntime which is also a
// template class that then inherits its template, which in this case is
// jsi::Runtime. So the inheritance is: ReanimatedHermesRuntime ->
// WithRuntimeDecorator -> DecoratedRuntime -> jsi::Runtime You can find out
// more about this in ReactCommon/jsi/jsi/Decorator.h or by following this link:
// https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/jsi/jsi/decorator.h
class ReanimatedHermesRuntime
: public jsi::WithRuntimeDecorator<ReanimatedReentrancyCheck> {
public:
ReanimatedHermesRuntime(
std::unique_ptr<facebook::hermes::HermesRuntime> runtime,
const std::shared_ptr<MessageQueueThread> &jsQueue,
const std::string &name);
~ReanimatedHermesRuntime();
private:
std::unique_ptr<facebook::hermes::HermesRuntime> runtime_;
ReanimatedReentrancyCheck reentrancyCheck_;
#if HERMES_ENABLE_DEBUGGER
#if REACT_NATIVE_MINOR_VERSION >= 71
chrome::DebugSessionToken debugToken_;
#endif // REACT_NATIVE_MINOR_VERSION >= 71
#endif // HERMES_ENABLE_DEBUGGER
};
} // namespace reanimated
#endif // JS_RUNTIME_HERMES

View File

@@ -0,0 +1,56 @@
#include "ReanimatedRuntime.h"
#include <cxxreact/MessageQueueThread.h>
#include <jsi/jsi.h>
#include <memory>
#include <utility>
#if JS_RUNTIME_HERMES
#include "ReanimatedHermesRuntime.h"
#elif JS_RUNTIME_V8
#include <v8runtime/V8RuntimeFactory.h>
#else
#if REACT_NATIVE_MINOR_VERSION >= 71
#include <jsc/JSCRuntime.h>
#else
#include <jsi/JSCRuntime.h>
#endif // REACT_NATIVE_MINOR_VERSION
#endif // JS_RUNTIME
namespace reanimated {
using namespace facebook;
using namespace react;
std::shared_ptr<jsi::Runtime> ReanimatedRuntime::make(
jsi::Runtime &rnRuntime,
const std::shared_ptr<MessageQueueThread> &jsQueue,
const std::string &name) {
(void)rnRuntime; // used only for V8
#if JS_RUNTIME_HERMES
// We don't call `jsQueue->quitSynchronous()` here, since it will be done
// later in ReanimatedHermesRuntime
auto runtime = facebook::hermes::makeHermesRuntime();
return std::make_shared<ReanimatedHermesRuntime>(
std::move(runtime), jsQueue, name);
#elif JS_RUNTIME_V8
// This is required by iOS, because there is an assertion in the destructor
// that the thread was indeed `quit` before.
jsQueue->quitSynchronous();
auto config = std::make_unique<rnv8::V8RuntimeConfig>();
config->enableInspector = false;
config->appName = name;
return rnv8::createSharedV8Runtime(&rnRuntime, std::move(config));
#else
// This is required by iOS, because there is an assertion in the destructor
// that the thread was indeed `quit` before
jsQueue->quitSynchronous();
return facebook::jsc::makeJSCRuntime();
#endif
}
} // namespace reanimated

View File

@@ -0,0 +1,30 @@
#pragma once
// JS_RUNTIME_HERMES is only set on Android so we have to check __has_include
// on iOS.
#if __APPLE__ && \
(__has_include( \
<reacthermes/HermesExecutorFactory.h>) || __has_include(<hermes/hermes.h>))
#define JS_RUNTIME_HERMES 1
#endif
#include <cxxreact/MessageQueueThread.h>
#include <jsi/jsi.h>
#include <memory>
#include <string>
namespace reanimated {
using namespace facebook;
using namespace react;
class ReanimatedRuntime {
public:
static std::shared_ptr<jsi::Runtime> make(
jsi::Runtime &rnRuntime,
const std::shared_ptr<MessageQueueThread> &jsQueue,
const std::string &name);
};
} // namespace reanimated

View File

@@ -0,0 +1,191 @@
# Hermes Runtime initialization
_Last updated_: 13/09/2022 by @Kwasow
This document describes the current way of initializing Hermes and connecting
it to the debugger. The work I did was mainly based on
[HermesExecutorFactory.cpp](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.cpp)
from React Native.
## Runtime initialization
If you take a look at `NativeProxy` (both on Android and iOS) you'll find
that it only makes a call to `ReanimatedRuntime::make(jsQueue)`. This
static function will return the correct runtime based on the user's configuration.
The initialization process is pretty simple and has only been moved out of
`NativeProxy` into `ReanimatedRuntime` without any major changes.
## Hermes runtime debugging
To enable debugging on the Hermes runtime we need to do two things:
1. Include source maps in JavaScript files
This part is done purely in JavaScript via the Babel plugin. The `makeWorklet`
function received an AST tree, which is aware of the modifications it made to
the code and therefore can generate the necessary source maps. It is important
that when we want to create a string from the AST we use the `generate` function
and enable source map generation so line mappings are not lost. Then when
transforming that code (ex. with `transformSync`) we have to pass the source
map as input so it can be updated.
Source map settings should always be set to `inline` so they are automatically
appended to the source code. The generated source map will be a base64 encoded
json.
A workletized function would look like this (after formattings):
```js
function _f(number) {
console.log(_WORKLET, number);
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IkFBYXNCLFNBQUNBLEVBQUQsQ0FBQ0EsTUFBRCxFQUFvQjtBQUV0Q0MsU0FBTyxDQUFDQyxHQUFSRCxDQUFZRSxRQUFaRixFQUFzQkQsTUFBdEJDO0FBRmtCIiwibmFtZXMiOlsibnVtYmVyIiwiY29uc29sZSIsImxvZyIsIl9XT1JLTEVUIl0sInNvdXJjZXMiOlsiL1VzZXJzL2thcm9sL0dpdC9yZWFjdC1uYXRpdmUtcmVhbmltYXRlZC9GYWJyaWNFeGFtcGxlL3NyYy9Xb3JrbGV0RXhhbXBsZS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiLyogZ2xvYmFsIF9XT1JLTEVUICovXG5pbXBvcnQgeyBCdXR0b24sIFZpZXcsIFN0eWxlU2hlZXQgfSBmcm9tICdyZWFjdC1uYXRpdmUnO1xuaW1wb3J0IHtcbiAgcnVuT25KUyxcbiAgcnVuT25VSSxcbiAgdXNlRGVyaXZlZFZhbHVlLFxuICB1c2VTaGFyZWRWYWx1ZSxcbn0gZnJvbSAncmVhY3QtbmF0aXZlLXJlYW5pbWF0ZWQnO1xuXG5pbXBvcnQgUmVhY3QgZnJvbSAncmVhY3QnO1xuXG5leHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBXb3JrbGV0RXhhbXBsZSgpIHtcbiAgLy8gcnVuT25VSSBkZW1vXG4gIGNvbnN0IHNvbWVXb3JrbGV0ID0gKG51bWJlcjogbnVtYmVyKSA9PiB7XG4gICAgJ3dvcmtsZXQnO1xuICAgIGNvbnNvbGUubG9nKF9XT1JLTEVULCBudW1iZXIpOyAvLyBfV09SS0xFVCBzaG91bGQgYmUgdHJ1ZVxuICB9O1xuXG4gIGNvbnN0IGhhbmRsZVByZXNzMSA9ICgpID0+IHtcbiAgICBydW5PblVJKHNvbWVXb3JrbGV0KShNYXRoLnJhbmRvbSgpKTtcbiAgfTtcblxuICAvLyBydW5PbkpTIGRlbW9cbiAgY29uc3QgeCA9IHVzZVNoYXJlZFZhbHVlKDApO1xuXG4gIGNvbnN0IHNvbWVGdW5jdGlvbiA9IChudW1iZXI6IG51bWJlcikgPT4ge1xuICAgIGNvbnNvbGUubG9nKF9XT1JLTEVULCBudW1iZXIpOyAvLyBfV09SS0xFVCBzaG91bGQgYmUgZmFsc2VcbiAgfTtcblxuICB1c2VEZXJpdmVkVmFsdWUoKCkgPT4ge1xuICAgIHJ1bk9uSlMoc29tZUZ1bmN0aW9uKSh4LnZhbHVlKTtcbiAgfSk7XG5cbiAgY29uc3QgaGFuZGxlUHJlc3MyID0gKCkgPT4ge1xuICAgIHgudmFsdWUgPSBNYXRoLnJhbmRvbSgpO1xuICB9O1xuXG4gIHJldHVybiAoXG4gICAgPFZpZXcgc3R5bGU9e3N0eWxlcy5jb250YWluZXJ9PlxuICAgICAgPEJ1dHRvbiBvblByZXNzPXtoYW5kbGVQcmVzczF9IHRpdGxlPVwicnVuT25VSSBkZW1vXCIgLz5cbiAgICAgIDxCdXR0b24gb25QcmVzcz17aGFuZGxlUHJlc3MyfSB0aXRsZT1cInJ1bk9uSlMgZGVtb1wiIC8+XG4gICAgPC9WaWV3PlxuICApO1xufVxuXG5jb25zdCBzdHlsZXMgPSBTdHlsZVNoZWV0LmNyZWF0ZSh7XG4gIGNvbnRhaW5lcjoge1xuICAgIGZsZXg6IDEsXG4gICAgYWxpZ25JdGVtczogJ2NlbnRlcicsXG4gICAganVzdGlmeUNvbnRlbnQ6ICdjZW50ZXInLFxuICB9LFxufSk7XG4iXX0=
```
And the base64 string after decoding is:
```json
{
"version": 3,
"mappings": "AAasB,SAACA,EAAD,CAACA,MAAD,EAAoB;AAEtCC,SAAO,CAACC,GAARD,CAAYE,QAAZF,EAAsBD,MAAtBC;AAFkB",
"names": ["number", "console", "log", "_WORKLET"],
"sources": [
"/Users/karol/Git/react-native-reanimated/FabricExample/src/WorkletExample.tsx"
],
"sourcesContent": [
"/* global _WORKLET */\nimport { Button, View, StyleSheet } from 'react-native';\nimport {\n runOnJS,\n runOnUI,\n useDerivedValue,\n useSharedValue,\n} from 'react-native-reanimated';\n\nimport React from 'react';\n\nexport default function WorkletExample() {\n // runOnUI demo\n const someWorklet = (number: number) => {\n 'worklet';\n console.log(_WORKLET, number); // _WORKLET should be true\n };\n\n const handlePress1 = () => {\n runOnUI(someWorklet)(Math.random());\n };\n\n // runOnJS demo\n const x = useSharedValue(0);\n\n const someFunction = (number: number) => {\n console.log(_WORKLET, number); // _WORKLET should be false\n };\n\n useDerivedValue(() => {\n runOnJS(someFunction)(x.value);\n });\n\n const handlePress2 = () => {\n x.value = Math.random();\n };\n\n return (\n <View style={styles.container}>\n <Button onPress={handlePress1} title=\"runOnUI demo\" />\n <Button onPress={handlePress2} title=\"runOnJS demo\" />\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n },\n});\n"
]
}
```
We run jest tests in release mode, because source maps will contain absolute
paths, which will be different on every machine and therefore would also alter
worklet hashes. Running in release mode prevents this.
2. Enable debugging on the runtime object
This is done by creating an adapter (`HermesExecutorRuntimeAdapter` inside of
`ReanimatedHermesRuntime.cpp`) which holds the runtime and allows the debugger
to communicate with it. The adapter is managed by a `Connection` (`ConnectionDemux`)
object, but this is not important in our case. We just have to make a call
to `facebook::hermes::inspector::chrome::enableDebugging()` and pass the adapter
and runtime name as parameters.
It is important to also `disableDebugging()` before the runtime is destroyed.
Failing to do so will probably crash the app as the debugger will try to
connect to a non-existent runtime.
The runtime should also be destroyed before the Reanimated module, because
otherwise there might be weird BAD_ACCESS errors when the gc gets it
hand on the runtime.
## Metro endpoint
Flipper and Chrome DevTools in general use the `localhost:8081/json` (where `8081`
is the port metro is running on) endpoint of metro to get the list of debuggable
targets (runtimes). For a normal React Native app the output would look something
like this:
```json
[
{
"id": "0-1",
"description": "org.reactjs.native.example.FabricExample",
"title": "Hermes React Native",
"faviconUrl": "https://reactjs.org/favicon.ico",
"devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=%5B%3A%3A1%5D%3A8081%2Finspector%2Fdebug%3Fdevice%3D0%26page%3D1",
"type": "node",
"webSocketDebuggerUrl": "ws://[::1]:8081/inspector/debug?device=0&page=1",
"vm": "Hermes"
},
{
"id": "0--1",
"description": "org.reactjs.native.example.FabricExample",
"title": "React Native Experimental (Improved Chrome Reloads)",
"faviconUrl": "https://reactjs.org/favicon.ico",
"devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=%5B%3A%3A1%5D%3A8081%2Finspector%2Fdebug%3Fdevice%3D0%26page%3D-1",
"type": "node",
"webSocketDebuggerUrl": "ws://[::1]:8081/inspector/debug?device=0&page=-1",
"vm": "don't use"
}
]
```
For an Android app with Reanimated it should include the Reanimated runtime like
this:
```json
[
{
"id": "0-2",
"description": "com.fabricexample",
"title": "Reanimated Runtime",
"faviconUrl": "https://reactjs.org/favicon.ico",
"devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=%5B%3A%3A1%5D%3A8081%2Finspector%2Fdebug%3Fdevice%3D0%26page%3D3",
"type": "node",
"webSocketDebuggerUrl": "ws://[::1]:8081/inspector/debug?device=1&page=2",
"vm": "Hermes"
},
{
"id": "0-1",
"description": "com.fabricexample",
"title": "Hermes React Native",
"faviconUrl": "https://reactjs.org/favicon.ico",
"devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=%5B%3A%3A1%5D%3A8081%2Finspector%2Fdebug%3Fdevice%3D0%26page%3D2",
"type": "node",
"webSocketDebuggerUrl": "ws://[::1]:8081/inspector/debug?device=1&page=1",
"vm": "Hermes"
},
{
"id": "0--1",
"description": "com.fabricexample",
"title": "React Native Experimental (Improved Chrome Reloads)",
"faviconUrl": "https://reactjs.org/favicon.ico",
"devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=%5B%3A%3A1%5D%3A8081%2Finspector%2Fdebug%3Fdevice%3D0%26page%3D-1",
"type": "node",
"webSocketDebuggerUrl": "ws://[::1]:8081/inspector/debug?device=1&page=-1",
"vm": "don't use"
},
{
"id": "0--2",
"description": "com.fabricexample",
"title": "Reanimated Runtime Experimental (Improved Chrome Reloads)",
"faviconUrl": "https://reactjs.org/favicon.ico",
"devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=%5B%3A%3A1%5D%3A8081%2Finspector%2Fdebug%3Fdevice%3D0%26page%3D-2",
"type": "node",
"webSocketDebuggerUrl": "ws://[::1]:8081/inspector/debug?device=1&page=-2",
"vm": "don't use"
}
]
```
Runtimes with negative IDs are 'virtual' - they are just references to the real
runtimes but their IDs don't change after a reload. If we were to connect to
the normal runtime and reload the app it would crash, as the debugger would try
to communicate with a non-existent runtime. These 'virtual' runtimes are made
and managed by metro (PR: [facebook/metro#864](https://github.com/facebook/metro/pull/864)).
## Known issues
**IFrame sandboxing**
Source maps always define a `sources` array, which contain names of files used
to generate the source map. For Chrome DevTools this is sufficient as it will
read files from disk, but the `IFrame` interface used by Flipper is sandboxed
and doesn't allow filesystem access. Therefore we also need to include the files
content in the `sourcesContent` array.
**Chrome version 105.0.5195.102 doesn't load source maps**
This version of Chrome introduced a regression into DevTools that broke source
maps loading for node.js apps. This issue is not caused by Reanimated in any
way and should be fixed by Chrome developers in later versions.
The issue was tracked here: https://bugs.chromium.org/p/chromium/issues/detail?id=1358497
**App reloads don't work**
On iOS the app will crash on every reload if a debugger is connected to the runtime.
On Android it will also crash but only after a few reloads.

View File

@@ -0,0 +1,149 @@
#include "WorkletRuntime.h"
#include "JSISerializer.h"
#include "ReanimatedRuntime.h"
#include "WorkletRuntimeCollector.h"
#include "WorkletRuntimeDecorator.h"
#include <jsi/decorator.h>
namespace reanimated {
class AroundLock {
const std::shared_ptr<std::recursive_mutex> mutex_;
public:
explicit AroundLock(const std::shared_ptr<std::recursive_mutex> &mutex)
: mutex_(mutex) {}
void before() const {
mutex_->lock();
}
void after() const {
mutex_->unlock();
}
};
class LockableRuntime : public jsi::WithRuntimeDecorator<AroundLock> {
AroundLock aroundLock_;
std::shared_ptr<jsi::Runtime> runtime_;
public:
explicit LockableRuntime(
std::shared_ptr<jsi::Runtime> &runtime,
const std::shared_ptr<std::recursive_mutex> &runtimeMutex)
: jsi::WithRuntimeDecorator<AroundLock>(*runtime, aroundLock_),
aroundLock_(runtimeMutex),
runtime_(std::move(runtime)) {}
};
static std::shared_ptr<jsi::Runtime> makeRuntime(
jsi::Runtime &runtime,
const std::shared_ptr<MessageQueueThread> &jsQueue,
const std::string &name,
const bool supportsLocking,
const std::shared_ptr<std::recursive_mutex> &runtimeMutex) {
auto reanimatedRuntime = ReanimatedRuntime::make(runtime, jsQueue, name);
if (supportsLocking) {
return std::make_shared<LockableRuntime>(reanimatedRuntime, runtimeMutex);
} else {
return reanimatedRuntime;
}
}
WorkletRuntime::WorkletRuntime(
jsi::Runtime &rnRuntime,
const std::shared_ptr<MessageQueueThread> &jsQueue,
const std::shared_ptr<JSScheduler> &jsScheduler,
const std::string &name,
const bool supportsLocking,
const std::string &valueUnpackerCode)
: runtimeMutex_(std::make_shared<std::recursive_mutex>()),
runtime_(makeRuntime(
rnRuntime,
jsQueue,
name,
supportsLocking,
runtimeMutex_)),
#ifndef NDEBUG
supportsLocking_(supportsLocking),
#endif
name_(name) {
jsi::Runtime &rt = *runtime_;
WorkletRuntimeCollector::install(rt);
WorkletRuntimeDecorator::decorate(rt, name, jsScheduler);
auto codeBuffer = std::make_shared<const jsi::StringBuffer>(
"(" + valueUnpackerCode + "\n)");
auto valueUnpacker = rt.evaluateJavaScript(codeBuffer, "valueUnpacker")
.asObject(rt)
.asFunction(rt);
rt.global().setProperty(rt, "__valueUnpacker", valueUnpacker);
}
jsi::Value WorkletRuntime::executeSync(
jsi::Runtime &rt,
const jsi::Value &worklet) const {
assert(
supportsLocking_ &&
("[Reanimated] Runtime \"" + name_ + "\" doesn't support locking.")
.c_str());
auto shareableWorklet = extractShareableOrThrow<ShareableWorklet>(
rt,
worklet,
"[Reanimated] Only worklets can be executed synchronously on UI runtime.");
auto lock = std::unique_lock<std::recursive_mutex>(*runtimeMutex_);
jsi::Runtime &uiRuntime = getJSIRuntime();
auto result = runGuarded(shareableWorklet);
auto shareableResult = extractShareableOrThrow(uiRuntime, result);
lock.unlock();
return shareableResult->getJSValue(rt);
}
jsi::Value WorkletRuntime::get(
jsi::Runtime &rt,
const jsi::PropNameID &propName) {
auto name = propName.utf8(rt);
if (name == "toString") {
return jsi::Function::createFromHostFunction(
rt,
propName,
0,
[this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t)
-> jsi::Value {
return jsi::String::createFromUtf8(rt, toString());
});
}
if (name == "name") {
return jsi::String::createFromUtf8(rt, name_);
}
return jsi::Value::undefined();
}
std::vector<jsi::PropNameID> WorkletRuntime::getPropertyNames(
jsi::Runtime &rt) {
std::vector<jsi::PropNameID> result;
result.push_back(jsi::PropNameID::forUtf8(rt, "toString"));
result.push_back(jsi::PropNameID::forUtf8(rt, "name"));
return result;
}
std::shared_ptr<WorkletRuntime> extractWorkletRuntime(
jsi::Runtime &rt,
const jsi::Value &value) {
return value.getObject(rt).getHostObject<WorkletRuntime>(rt);
}
void scheduleOnRuntime(
jsi::Runtime &rt,
const jsi::Value &workletRuntimeValue,
const jsi::Value &shareableWorkletValue) {
auto workletRuntime = extractWorkletRuntime(rt, workletRuntimeValue);
auto shareableWorklet = extractShareableOrThrow<ShareableWorklet>(
rt,
shareableWorkletValue,
"[Reanimated] Function passed to `_scheduleOnRuntime` is not a shareable worklet. Please make sure that `processNestedWorklets` option in Reanimated Babel plugin is enabled.");
workletRuntime->runAsyncGuarded(shareableWorklet);
}
} // namespace reanimated

View File

@@ -0,0 +1,85 @@
#pragma once
#include <cxxreact/MessageQueueThread.h>
#include <jsi/jsi.h>
#include "AsyncQueue.h"
#include "JSScheduler.h"
#include "Shareables.h"
#include <memory>
#include <string>
#include <thread>
#include <utility>
#include <vector>
using namespace facebook;
using namespace react;
namespace reanimated {
class WorkletRuntime : public jsi::HostObject,
public std::enable_shared_from_this<WorkletRuntime> {
public:
explicit WorkletRuntime(
jsi::Runtime &rnRuntime,
const std::shared_ptr<MessageQueueThread> &jsQueue,
const std::shared_ptr<JSScheduler> &jsScheduler,
const std::string &name,
const bool supportsLocking,
const std::string &valueUnpackerCode);
jsi::Runtime &getJSIRuntime() const {
return *runtime_;
}
template <typename... Args>
inline jsi::Value runGuarded(
const std::shared_ptr<ShareableWorklet> &shareableWorklet,
Args &&...args) const {
jsi::Runtime &rt = *runtime_;
return runOnRuntimeGuarded(
rt, shareableWorklet->getJSValue(rt), std::forward<Args>(args)...);
}
void runAsyncGuarded(
const std::shared_ptr<ShareableWorklet> &shareableWorklet) {
if (queue_ == nullptr) {
queue_ = std::make_shared<AsyncQueue>(name_);
}
queue_->push(
[=, self = shared_from_this()] { self->runGuarded(shareableWorklet); });
}
jsi::Value executeSync(jsi::Runtime &rt, const jsi::Value &worklet) const;
std::string toString() const {
return "[WorkletRuntime \"" + name_ + "\"]";
}
jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propName) override;
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;
private:
const std::shared_ptr<std::recursive_mutex> runtimeMutex_;
const std::shared_ptr<jsi::Runtime> runtime_;
#ifndef NDEBUG
const bool supportsLocking_;
#endif
const std::string name_;
std::shared_ptr<AsyncQueue> queue_;
};
// This function needs to be non-inline to avoid problems with dynamic_cast on
// Android
std::shared_ptr<WorkletRuntime> extractWorkletRuntime(
jsi::Runtime &rt,
const jsi::Value &value);
void scheduleOnRuntime(
jsi::Runtime &rt,
const jsi::Value &workletRuntimeValue,
const jsi::Value &shareableWorkletValue);
} // namespace reanimated

View File

@@ -0,0 +1,36 @@
#pragma once
#include "WorkletRuntimeRegistry.h"
#include <jsi/jsi.h>
#include <memory>
namespace reanimated {
class WorkletRuntimeCollector : public jsi::HostObject {
// When worklet runtime is created, we inject an instance of this class as a
// `jsi::HostObject` into the global object. When worklet runtime is
// terminated, the object is garbage-collected, which runs the C++ destructor.
// In the destructor, we unregister the worklet runtime from the registry.
public:
explicit WorkletRuntimeCollector(jsi::Runtime &runtime) : runtime_(runtime) {
WorkletRuntimeRegistry::registerRuntime(runtime_);
}
~WorkletRuntimeCollector() {
WorkletRuntimeRegistry::unregisterRuntime(runtime_);
}
static void install(jsi::Runtime &rt) {
auto collector = std::make_shared<WorkletRuntimeCollector>(rt);
auto object = jsi::Object::createFromHostObject(rt, collector);
rt.global().setProperty(rt, "__workletRuntimeCollector", object);
}
private:
jsi::Runtime &runtime_;
};
} // namespace reanimated

View File

@@ -0,0 +1,149 @@
#include "WorkletRuntimeDecorator.h"
#include "JSISerializer.h"
#include "ReanimatedJSIUtils.h"
#include "Shareables.h"
#include "WorkletRuntime.h"
#ifdef ANDROID
#include "Logger.h"
#else
#include "Common/cpp/hidden_headers/Logger.h"
#endif
namespace reanimated {
static inline double performanceNow() {
// copied from JSExecutor.cpp
auto time = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
time.time_since_epoch())
.count();
constexpr double NANOSECONDS_IN_MILLISECOND = 1000000.0;
return duration / NANOSECONDS_IN_MILLISECOND;
}
void WorkletRuntimeDecorator::decorate(
jsi::Runtime &rt,
const std::string &name,
const std::shared_ptr<JSScheduler> &jsScheduler) {
// resolves "ReferenceError: Property 'global' doesn't exist at ..."
rt.global().setProperty(rt, "global", rt.global());
rt.global().setProperty(rt, "_WORKLET", true);
rt.global().setProperty(rt, "_LABEL", jsi::String::createFromAscii(rt, name));
#ifdef RCT_NEW_ARCH_ENABLED
constexpr auto isFabric = true;
#else
constexpr auto isFabric = false;
#endif // RCT_NEW_ARCH_ENABLED
rt.global().setProperty(rt, "_IS_FABRIC", isFabric);
#ifndef NDEBUG
auto evalWithSourceUrl = [](jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count) -> jsi::Value {
auto code = std::make_shared<const jsi::StringBuffer>(
args[0].asString(rt).utf8(rt));
std::string url;
if (count > 1 && args[1].isString()) {
url = args[1].asString(rt).utf8(rt);
}
return rt.evaluateJavaScript(code, url);
};
rt.global().setProperty(
rt,
"evalWithSourceUrl",
jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "evalWithSourceUrl"),
1,
evalWithSourceUrl));
#endif // NDEBUG
jsi_utils::installJsiFunction(
rt, "_toString", [](jsi::Runtime &rt, const jsi::Value &value) {
return jsi::String::createFromUtf8(rt, stringifyJSIValue(rt, value));
});
jsi_utils::installJsiFunction(
rt, "_log", [](jsi::Runtime &rt, const jsi::Value &value) {
Logger::log(stringifyJSIValue(rt, value));
});
jsi_utils::installJsiFunction(
rt,
"_makeShareableClone",
[](jsi::Runtime &rt,
const jsi::Value &value,
const jsi::Value &nativeStateSource) {
auto shouldRetainRemote = jsi::Value::undefined();
return reanimated::makeShareableClone(
rt, value, shouldRetainRemote, nativeStateSource);
});
jsi_utils::installJsiFunction(
rt,
"_scheduleOnJS",
[jsScheduler](
jsi::Runtime &rt,
const jsi::Value &remoteFun,
const jsi::Value &argsValue) {
auto shareableRemoteFun = extractShareableOrThrow<
ShareableRemoteFunction>(
rt,
remoteFun,
"[Reanimated] Incompatible object passed to scheduleOnJS. It is only allowed to schedule worklets or functions defined on the React Native JS runtime this way.");
auto shareableArgs = argsValue.isUndefined()
? nullptr
: extractShareableOrThrow<ShareableArray>(
rt, argsValue, "[Reanimated] Args must be an array.");
jsScheduler->scheduleOnJS([=](jsi::Runtime &rt) {
auto remoteFun = shareableRemoteFun->getJSValue(rt);
if (shareableArgs == nullptr) {
// fast path for remote function w/o arguments
remoteFun.asObject(rt).asFunction(rt).call(rt);
} else {
auto argsArray =
shareableArgs->getJSValue(rt).asObject(rt).asArray(rt);
auto argsSize = argsArray.size(rt);
// number of arguments is typically relatively small so it is ok to
// to use VLAs here, hence disabling the lint rule
jsi::Value args[argsSize]; // NOLINT(runtime/arrays)
for (size_t i = 0; i < argsSize; i++) {
args[i] = argsArray.getValueAtIndex(rt, i);
}
remoteFun.asObject(rt).asFunction(rt).call(rt, args, argsSize);
}
});
});
jsi_utils::installJsiFunction(
rt,
"_scheduleOnRuntime",
[](jsi::Runtime &rt,
const jsi::Value &workletRuntimeValue,
const jsi::Value &shareableWorkletValue) {
reanimated::scheduleOnRuntime(
rt, workletRuntimeValue, shareableWorkletValue);
});
jsi::Object performance(rt);
performance.setProperty(
rt,
"now",
jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "now"),
0,
[](jsi::Runtime &runtime,
const jsi::Value &,
const jsi::Value *args,
size_t count) { return jsi::Value(performanceNow()); }));
rt.global().setProperty(rt, "performance", performance);
}
} // namespace reanimated

View File

@@ -0,0 +1,22 @@
#pragma once
#include "JSScheduler.h"
#include <jsi/jsi.h>
#include <memory>
#include <string>
using namespace facebook;
namespace reanimated {
class WorkletRuntimeDecorator {
public:
static void decorate(
jsi::Runtime &rt,
const std::string &name,
const std::shared_ptr<JSScheduler> &jsScheduler);
};
} // namespace reanimated

View File

@@ -0,0 +1,8 @@
#include "WorkletRuntimeRegistry.h"
namespace reanimated {
std::set<jsi::Runtime *> WorkletRuntimeRegistry::registry_{};
std::mutex WorkletRuntimeRegistry::mutex_{};
} // namespace reanimated

View File

@@ -0,0 +1,39 @@
#pragma once
#include <jsi/jsi.h>
#include <mutex>
#include <set>
using namespace facebook;
namespace reanimated {
class WorkletRuntimeRegistry {
private:
static std::set<jsi::Runtime *> registry_;
static std::mutex mutex_; // Protects `registry_`.
WorkletRuntimeRegistry() {} // private ctor
static void registerRuntime(jsi::Runtime &runtime) {
std::lock_guard<std::mutex> lock(mutex_);
registry_.insert(&runtime);
}
static void unregisterRuntime(jsi::Runtime &runtime) {
std::lock_guard<std::mutex> lock(mutex_);
registry_.erase(&runtime);
}
friend class WorkletRuntimeCollector;
public:
static bool isRuntimeAlive(jsi::Runtime *runtime) {
assert(runtime != nullptr);
std::lock_guard<std::mutex> lock(mutex_);
return registry_.find(runtime) != registry_.end();
}
};
} // namespace reanimated

View File

@@ -0,0 +1,93 @@
#include "EventHandlerRegistry.h"
#include "WorkletEventHandler.h"
#include <utility>
namespace reanimated {
void EventHandlerRegistry::registerEventHandler(
const std::shared_ptr<WorkletEventHandler> &eventHandler) {
const std::lock_guard<std::mutex> lock(instanceMutex);
const auto &eventName = eventHandler->getEventName();
auto handlerId = eventHandler->getHandlerId();
if (eventHandler->shouldIgnoreEmitterReactTag()) {
eventMappingsWithoutTag[eventName][handlerId] = eventHandler;
} else {
const auto emitterReactTag = eventHandler->getEmitterReactTag();
const auto eventHash = std::make_pair(emitterReactTag, eventName);
eventMappingsWithTag[eventHash][handlerId] = eventHandler;
}
eventHandlers[handlerId] = eventHandler;
}
void EventHandlerRegistry::unregisterEventHandler(const uint64_t id) {
const std::lock_guard<std::mutex> lock(instanceMutex);
auto handlerIt = eventHandlers.find(id);
if (handlerIt != eventHandlers.end()) {
const auto &eventHandler = handlerIt->second;
const auto &eventName = eventHandler->getEventName();
if (eventHandler->shouldIgnoreEmitterReactTag()) {
const auto eventMappingIt = eventMappingsWithoutTag.find(eventName);
auto &handlersMap = eventMappingIt->second;
handlersMap.erase(id);
if (handlersMap.empty()) {
eventMappingsWithoutTag.erase(eventMappingIt);
}
} else {
const auto emitterReactTag = eventHandler->getEmitterReactTag();
const auto eventHash = std::make_pair(emitterReactTag, eventName);
const auto eventMappingIt = eventMappingsWithTag.find(eventHash);
auto &handlersMap = eventMappingIt->second;
handlersMap.erase(id);
if (handlersMap.empty()) {
eventMappingsWithTag.erase(eventMappingIt);
}
}
eventHandlers.erase(handlerIt);
}
}
void EventHandlerRegistry::processEvent(
const std::shared_ptr<WorkletRuntime> &uiWorkletRuntime,
const double eventTimestamp,
const std::string &eventName,
const int emitterReactTag,
const jsi::Value &eventPayload) {
std::vector<std::shared_ptr<WorkletEventHandler>> handlersForEvent;
{
const std::lock_guard<std::mutex> lock(instanceMutex);
auto handlersIt = eventMappingsWithoutTag.find(eventName);
if (handlersIt != eventMappingsWithoutTag.end()) {
for (auto handler : handlersIt->second) {
handlersForEvent.push_back(handler.second);
}
}
const auto eventHash = std::make_pair(emitterReactTag, eventName);
auto handlersWithTagIt = eventMappingsWithTag.find(eventHash);
if (handlersWithTagIt != eventMappingsWithTag.end()) {
for (auto handler : handlersWithTagIt->second) {
handlersForEvent.push_back(handler.second);
}
}
}
jsi::Runtime &rt = uiWorkletRuntime->getJSIRuntime();
eventPayload.asObject(rt).setProperty(
rt, "eventName", jsi::String::createFromUtf8(rt, eventName));
for (auto handler : handlersForEvent) {
handler->process(uiWorkletRuntime, eventTimestamp, eventPayload);
}
}
bool EventHandlerRegistry::isAnyHandlerWaitingForEvent(
const std::string &eventName,
const int emitterReactTag) {
const std::lock_guard<std::mutex> lock(instanceMutex);
const auto eventHash = std::make_pair(emitterReactTag, eventName);
auto it = eventMappingsWithTag.find(eventHash);
return it != eventMappingsWithTag.end() && !it->second.empty();
}
} // namespace reanimated

View File

@@ -0,0 +1,51 @@
#pragma once
#include <jsi/jsi.h>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "WorkletRuntime.h"
using namespace facebook;
namespace reanimated {
class WorkletEventHandler;
class EventHandlerRegistry {
std::map<
std::pair<int, std::string>,
std::unordered_map<uint64_t, std::shared_ptr<WorkletEventHandler>>>
eventMappingsWithTag;
std::map<
std::string,
std::unordered_map<uint64_t, std::shared_ptr<WorkletEventHandler>>>
eventMappingsWithoutTag;
std::map<uint64_t, std::shared_ptr<WorkletEventHandler>> eventHandlers;
std::mutex instanceMutex;
public:
void registerEventHandler(
const std::shared_ptr<WorkletEventHandler> &eventHandler);
void unregisterEventHandler(const uint64_t id);
void processEvent(
const std::shared_ptr<WorkletRuntime> &uiWorkletRuntime,
const double eventTimestamp,
const std::string &eventName,
const int emitterReactTag,
const jsi::Value &eventPayload);
bool isAnyHandlerWaitingForEvent(
const std::string &eventName,
const int emitterReactTag);
};
} // namespace reanimated

View File

@@ -0,0 +1,324 @@
#include "Shareables.h"
using namespace facebook;
namespace reanimated {
jsi::Function getValueUnpacker(jsi::Runtime &rt) {
auto valueUnpacker = rt.global().getProperty(rt, "__valueUnpacker");
assert(valueUnpacker.isObject() && "valueUnpacker not found");
return valueUnpacker.asObject(rt).asFunction(rt);
}
#ifndef NDEBUG
static const auto callGuardLambda = [](facebook::jsi::Runtime &rt,
const facebook::jsi::Value &thisVal,
const facebook::jsi::Value *args,
size_t count) {
return args[0].asObject(rt).asFunction(rt).call(rt, args + 1, count - 1);
};
jsi::Function getCallGuard(jsi::Runtime &rt) {
auto callGuard = rt.global().getProperty(rt, "__callGuardDEV");
if (callGuard.isObject()) {
// Use JS implementation if `__callGuardDEV` has already been installed.
// This is the desired behavior.
return callGuard.asObject(rt).asFunction(rt);
}
// Otherwise, fallback to C++ JSI implementation. This is necessary so that we
// can install `__callGuardDEV` itself and should happen only once. Note that
// the C++ implementation doesn't intercept errors and simply throws them as
// C++ exceptions which crashes the app. We assume that installing the guard
// doesn't throw any errors.
return jsi::Function::createFromHostFunction(
rt, jsi::PropNameID::forAscii(rt, "callGuard"), 1, callGuardLambda);
}
#endif // NDEBUG
jsi::Value makeShareableClone(
jsi::Runtime &rt,
const jsi::Value &value,
const jsi::Value &shouldRetainRemote,
const jsi::Value &nativeStateSource) {
std::shared_ptr<Shareable> shareable;
if (value.isObject()) {
auto object = value.asObject(rt);
if (!object.getProperty(rt, "__workletHash").isUndefined()) {
shareable = std::make_shared<ShareableWorklet>(rt, object);
} else if (!object.getProperty(rt, "__init").isUndefined()) {
shareable = std::make_shared<ShareableHandle>(rt, object);
} else if (object.isFunction(rt)) {
auto function = object.asFunction(rt);
if (function.isHostFunction(rt)) {
shareable =
std::make_shared<ShareableHostFunction>(rt, std::move(function));
} else {
shareable =
std::make_shared<ShareableRemoteFunction>(rt, std::move(function));
}
} else if (object.isArray(rt)) {
if (shouldRetainRemote.isBool() && shouldRetainRemote.getBool()) {
shareable = std::make_shared<RetainingShareable<ShareableArray>>(
rt, object.asArray(rt));
} else {
shareable = std::make_shared<ShareableArray>(rt, object.asArray(rt));
}
} else if (object.isArrayBuffer(rt)) {
shareable =
std::make_shared<ShareableArrayBuffer>(rt, object.getArrayBuffer(rt));
} else if (object.isHostObject(rt)) {
if (object.isHostObject<ShareableJSRef>(rt)) {
return object;
}
shareable =
std::make_shared<ShareableHostObject>(rt, object.getHostObject(rt));
} else {
if (shouldRetainRemote.isBool() && shouldRetainRemote.getBool()) {
shareable = std::make_shared<RetainingShareable<ShareableObject>>(
rt, object, nativeStateSource);
} else {
shareable =
std::make_shared<ShareableObject>(rt, object, nativeStateSource);
}
}
} else if (value.isString()) {
shareable = std::make_shared<ShareableString>(value.asString(rt).utf8(rt));
} else if (value.isUndefined()) {
shareable = std::make_shared<ShareableScalar>();
} else if (value.isNull()) {
shareable = std::make_shared<ShareableScalar>(nullptr);
} else if (value.isBool()) {
shareable = std::make_shared<ShareableScalar>(value.getBool());
} else if (value.isNumber()) {
shareable = std::make_shared<ShareableScalar>(value.getNumber());
#if REACT_NATIVE_MINOR_VERSION >= 71
} else if (value.isBigInt()) {
shareable = std::make_shared<ShareableBigInt>(rt, value.getBigInt(rt));
#endif
} else if (value.isSymbol()) {
// TODO: this is only a placeholder implementation, here we replace symbols
// with strings in order to make certain objects to be captured. There isn't
// yet any usecase for using symbols on the UI runtime so it is fine to keep
// it like this for now.
shareable =
std::make_shared<ShareableString>(value.getSymbol(rt).toString(rt));
} else {
throw std::runtime_error(
"[Reanimated] Attempted to convert an unsupported value type.");
}
return ShareableJSRef::newHostObject(rt, shareable);
}
std::shared_ptr<Shareable> extractShareableOrThrow(
jsi::Runtime &rt,
const jsi::Value &maybeShareableValue,
const std::string &errorMessage) {
if (maybeShareableValue.isObject()) {
auto object = maybeShareableValue.asObject(rt);
if (object.isHostObject<ShareableJSRef>(rt)) {
return object.getHostObject<ShareableJSRef>(rt)->value();
}
throw std::runtime_error(
"[Reanimated] Attempted to extract from a HostObject that wasn't converted to a Shareable.");
} else if (maybeShareableValue.isUndefined()) {
return Shareable::undefined();
}
throw std::runtime_error(errorMessage);
}
Shareable::~Shareable() {}
std::shared_ptr<Shareable> Shareable::undefined() {
static auto undefined = std::make_shared<ShareableScalar>();
return undefined;
}
template <typename BaseClass>
jsi::Value RetainingShareable<BaseClass>::getJSValue(jsi::Runtime &rt) {
if (&rt == primaryRuntime_) {
// TODO: it is suboptimal to generate new object every time getJS is
// called on host runtime the objects we are generating already exists
// and we should possibly just grab a hold of such object and use it here
// instead of creating a new JS representation. As far as I understand the
// only case where it can be realistically called this way is when a
// shared value is created and then accessed on the same runtime
return BaseClass::toJSValue(rt);
}
if (secondaryValue_ == nullptr) {
auto value = BaseClass::toJSValue(rt);
secondaryValue_ = std::make_unique<jsi::Value>(rt, value);
secondaryRuntime_ = &rt;
return value;
}
if (&rt == secondaryRuntime_) {
return jsi::Value(rt, *secondaryValue_);
}
return BaseClass::toJSValue(rt);
}
ShareableJSRef::~ShareableJSRef() {}
ShareableArray::ShareableArray(jsi::Runtime &rt, const jsi::Array &array)
: Shareable(ArrayType) {
auto size = array.size(rt);
data_.reserve(size);
for (size_t i = 0; i < size; i++) {
data_.push_back(extractShareableOrThrow(rt, array.getValueAtIndex(rt, i)));
}
}
jsi::Value ShareableArray::toJSValue(jsi::Runtime &rt) {
auto size = data_.size();
auto ary = jsi::Array(rt, size);
for (size_t i = 0; i < size; i++) {
ary.setValueAtIndex(rt, i, data_[i]->getJSValue(rt));
}
return ary;
}
jsi::Value ShareableArrayBuffer::toJSValue(jsi::Runtime &rt) {
auto size = static_cast<int>(data_.size());
auto arrayBuffer = rt.global()
.getPropertyAsFunction(rt, "ArrayBuffer")
.callAsConstructor(rt, size)
.getObject(rt)
.getArrayBuffer(rt);
memcpy(arrayBuffer.data(rt), data_.data(), size);
return arrayBuffer;
}
ShareableObject::ShareableObject(jsi::Runtime &rt, const jsi::Object &object)
: Shareable(ObjectType) {
auto propertyNames = object.getPropertyNames(rt);
auto size = propertyNames.size(rt);
data_.reserve(size);
for (size_t i = 0; i < size; i++) {
auto key = propertyNames.getValueAtIndex(rt, i).asString(rt);
auto value = extractShareableOrThrow(rt, object.getProperty(rt, key));
data_.emplace_back(key.utf8(rt), value);
}
#if REACT_NATIVE_MINOR_VERSION >= 71
if (object.hasNativeState(rt)) {
nativeState_ = object.getNativeState(rt);
}
#endif
}
ShareableObject::ShareableObject(
jsi::Runtime &rt,
const jsi::Object &object,
const jsi::Value &nativeStateSource)
: ShareableObject(rt, object) {
#if REACT_NATIVE_MINOR_VERSION >= 71
if (nativeStateSource.isObject() &&
nativeStateSource.asObject(rt).hasNativeState(rt)) {
nativeState_ = nativeStateSource.asObject(rt).getNativeState(rt);
}
#endif
}
jsi::Value ShareableObject::toJSValue(jsi::Runtime &rt) {
auto obj = jsi::Object(rt);
for (size_t i = 0, size = data_.size(); i < size; i++) {
obj.setProperty(
rt, data_[i].first.c_str(), data_[i].second->getJSValue(rt));
}
#if REACT_NATIVE_MINOR_VERSION >= 71
if (nativeState_ != nullptr) {
obj.setNativeState(rt, nativeState_);
}
#endif
return obj;
}
jsi::Value ShareableHostObject::toJSValue(jsi::Runtime &rt) {
return jsi::Object::createFromHostObject(rt, hostObject_);
}
jsi::Value ShareableHostFunction::toJSValue(jsi::Runtime &rt) {
return jsi::Function::createFromHostFunction(
rt, jsi::PropNameID::forUtf8(rt, name_), paramCount_, hostFunction_);
}
jsi::Value ShareableWorklet::toJSValue(jsi::Runtime &rt) {
assert(
std::any_of(
data_.cbegin(),
data_.cend(),
[](const auto &item) { return item.first == "__workletHash"; }) &&
"ShareableWorklet doesn't have `__workletHash` property");
jsi::Value obj = ShareableObject::toJSValue(rt);
return getValueUnpacker(rt).call(
rt, obj, jsi::String::createFromAscii(rt, "Worklet"));
}
jsi::Value ShareableRemoteFunction::toJSValue(jsi::Runtime &rt) {
if (&rt == runtime_) {
return jsi::Value(rt, *function_);
} else {
#ifndef NDEBUG
return getValueUnpacker(rt).call(
rt,
ShareableJSRef::newHostObject(rt, shared_from_this()),
jsi::String::createFromAscii(rt, "RemoteFunction"),
jsi::String::createFromUtf8(rt, name_));
#else
return ShareableJSRef::newHostObject(rt, shared_from_this());
#endif
}
}
jsi::Value ShareableHandle::toJSValue(jsi::Runtime &rt) {
if (remoteValue_ == nullptr) {
auto initObj = initializer_->getJSValue(rt);
auto value = std::make_unique<jsi::Value>(getValueUnpacker(rt).call(
rt, initObj, jsi::String::createFromAscii(rt, "Handle")));
// We are locking the initialization here since the thread that is
// initalizing can be pre-empted on runtime lock. E.g.
// UI thread can be pre-empted on initialization of a shared value and then
// JS thread can try to access the shared value, locking the whole runtime.
// If we put the lock on `getValueUnpacker` part (basically any part that
// requires runtime) we would get a deadlock since UI thread would never
// release it.
std::unique_lock<std::mutex> lock(initializationMutex_);
if (remoteValue_ == nullptr) {
remoteValue_ = std::move(value);
remoteRuntime_ = &rt;
}
}
return jsi::Value(rt, *remoteValue_);
}
jsi::Value ShareableString::toJSValue(jsi::Runtime &rt) {
return jsi::String::createFromUtf8(rt, data_);
}
#if REACT_NATIVE_MINOR_VERSION >= 71
jsi::Value ShareableBigInt::toJSValue(jsi::Runtime &rt) {
return rt.global()
.getPropertyAsFunction(rt, "BigInt")
.call(rt, jsi::String::createFromUtf8(rt, string_));
}
#endif
jsi::Value ShareableScalar::toJSValue(jsi::Runtime &) {
switch (valueType_) {
case Shareable::UndefinedType:
return jsi::Value();
case Shareable::NullType:
return jsi::Value(nullptr);
case Shareable::BooleanType:
return jsi::Value(data_.boolean);
case Shareable::NumberType:
return jsi::Value(data_.number);
default:
throw std::runtime_error(
"[Reanimated] Attempted to convert object that's not of a scalar type.");
}
}
} /* namespace reanimated */

View File

@@ -0,0 +1,361 @@
#pragma once
#include <jsi/jsi.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "WorkletRuntimeRegistry.h"
using namespace facebook;
namespace reanimated {
jsi::Function getValueUnpacker(jsi::Runtime &rt);
#ifndef NDEBUG
jsi::Function getCallGuard(jsi::Runtime &rt);
#endif // NDEBUG
// If possible, please use `WorkletRuntime::runGuarded` instead.
template <typename... Args>
inline jsi::Value runOnRuntimeGuarded(
jsi::Runtime &rt,
const jsi::Value &function,
Args &&...args) {
// We only use callGuard in debug mode, otherwise we call the provided
// function directly. CallGuard provides a way of capturing exceptions in
// JavaScript and propagating them to the main React Native thread such that
// they can be presented using RN's LogBox.
#ifndef NDEBUG
return getCallGuard(rt).call(rt, function, args...);
#else
return function.asObject(rt).asFunction(rt).call(rt, args...);
#endif
}
inline void cleanupIfRuntimeExists(
jsi::Runtime *rt,
std::unique_ptr<jsi::Value> &value) {
if (rt != nullptr && !WorkletRuntimeRegistry::isRuntimeAlive(rt)) {
// The below use of unique_ptr.release prevents the smart pointer from
// calling the destructor of the kept object. This effectively results in
// leaking some memory. We do this on purpose, as sometimes we would keep
// references to JSI objects past the lifetime of its runtime (e.g.,
// shared values references from the RN VM holds reference to JSI objects
// on the UI runtime). When the UI runtime is terminated, the orphaned JSI
// objects would crash the app when their destructors are called, because
// they call into a memory that's managed by the terminated runtime. We
// accept the tradeoff of leaking memory here, as it has a limited impact.
// This scenario can only occur when the React instance is torn down which
// happens in development mode during app reloads, or in production when
// the app is being shut down gracefully by the system. An alternative
// solution would require us to keep track of all JSI values that are in
// use which would require additional data structure and compute spent on
// bookkeeping that only for the sake of destroying the values in time
// before the runtime is terminated. Note that the underlying memory that
// jsi::Value refers to is managed by the VM and gets freed along with the
// runtime.
value.release();
}
}
class Shareable {
protected:
virtual jsi::Value toJSValue(jsi::Runtime &rt) = 0;
public:
virtual ~Shareable();
enum ValueType {
UndefinedType,
NullType,
BooleanType,
NumberType,
// SymbolType, TODO
BigIntType,
StringType,
ObjectType,
ArrayType,
WorkletType,
RemoteFunctionType,
HandleType,
HostObjectType,
HostFunctionType,
ArrayBufferType,
};
explicit Shareable(ValueType valueType) : valueType_(valueType) {}
virtual jsi::Value getJSValue(jsi::Runtime &rt) {
return toJSValue(rt);
}
inline ValueType valueType() const {
return valueType_;
}
static std::shared_ptr<Shareable> undefined();
protected:
ValueType valueType_;
};
template <typename BaseClass>
class RetainingShareable : virtual public BaseClass {
private:
jsi::Runtime *primaryRuntime_;
jsi::Runtime *secondaryRuntime_;
std::unique_ptr<jsi::Value> secondaryValue_;
public:
template <typename... Args>
explicit RetainingShareable(jsi::Runtime &rt, Args &&...args)
: BaseClass(rt, std::forward<Args>(args)...), primaryRuntime_(&rt) {}
jsi::Value getJSValue(jsi::Runtime &rt);
~RetainingShareable() {
cleanupIfRuntimeExists(secondaryRuntime_, secondaryValue_);
}
};
class ShareableJSRef : public jsi::HostObject {
private:
const std::shared_ptr<Shareable> value_;
public:
explicit ShareableJSRef(const std::shared_ptr<Shareable> &value)
: value_(value) {}
virtual ~ShareableJSRef();
std::shared_ptr<Shareable> value() const {
return value_;
}
static jsi::Object newHostObject(
jsi::Runtime &rt,
const std::shared_ptr<Shareable> &value) {
return jsi::Object::createFromHostObject(
rt, std::make_shared<ShareableJSRef>(value));
}
};
jsi::Value makeShareableClone(
jsi::Runtime &rt,
const jsi::Value &value,
const jsi::Value &shouldRetainRemote,
const jsi::Value &nativeStateSource);
std::shared_ptr<Shareable> extractShareableOrThrow(
jsi::Runtime &rt,
const jsi::Value &maybeShareableValue,
const std::string &errorMessage =
"[Reanimated] Expecting the object to be of type ShareableJSRef.");
template <typename T>
std::shared_ptr<T> extractShareableOrThrow(
jsi::Runtime &rt,
const jsi::Value &shareableRef,
const std::string &errorMessage =
"[Reanimated] Provided shareable object is of an incompatible type.") {
auto res = std::dynamic_pointer_cast<T>(
extractShareableOrThrow(rt, shareableRef, errorMessage));
if (!res) {
throw std::runtime_error(errorMessage);
}
return res;
}
class ShareableArray : public Shareable {
public:
ShareableArray(jsi::Runtime &rt, const jsi::Array &array);
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
std::vector<std::shared_ptr<Shareable>> data_;
};
class ShareableObject : public Shareable {
public:
ShareableObject(jsi::Runtime &rt, const jsi::Object &object);
ShareableObject(
jsi::Runtime &rt,
const jsi::Object &object,
const jsi::Value &nativeStateSource);
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
std::vector<std::pair<std::string, std::shared_ptr<Shareable>>> data_;
#if REACT_NATIVE_MINOR_VERSION >= 71
std::shared_ptr<jsi::NativeState> nativeState_;
#endif
};
class ShareableHostObject : public Shareable {
public:
ShareableHostObject(
jsi::Runtime &,
const std::shared_ptr<jsi::HostObject> &hostObject)
: Shareable(HostObjectType), hostObject_(hostObject) {}
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
const std::shared_ptr<jsi::HostObject> hostObject_;
};
class ShareableHostFunction : public Shareable {
public:
ShareableHostFunction(jsi::Runtime &rt, jsi::Function function)
: Shareable(HostFunctionType),
hostFunction_(
(assert(function.isHostFunction(rt)),
function.getHostFunction(rt))),
name_(function.getProperty(rt, "name").asString(rt).utf8(rt)),
paramCount_(function.getProperty(rt, "length").asNumber()) {}
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
const jsi::HostFunctionType hostFunction_;
const std::string name_;
const unsigned int paramCount_;
};
class ShareableArrayBuffer : public Shareable {
public:
ShareableArrayBuffer(
jsi::Runtime &rt,
#if REACT_NATIVE_MINOR_VERSION >= 72
const jsi::ArrayBuffer &arrayBuffer
#else
jsi::ArrayBuffer arrayBuffer
#endif
)
: Shareable(ArrayBufferType),
data_(
arrayBuffer.data(rt),
arrayBuffer.data(rt) + arrayBuffer.size(rt)) {
}
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
const std::vector<uint8_t> data_;
};
class ShareableWorklet : public ShareableObject {
public:
ShareableWorklet(jsi::Runtime &rt, const jsi::Object &worklet)
: ShareableObject(rt, worklet) {
valueType_ = WorkletType;
}
jsi::Value toJSValue(jsi::Runtime &rt) override;
};
class ShareableRemoteFunction
: public Shareable,
public std::enable_shared_from_this<ShareableRemoteFunction> {
private:
jsi::Runtime *runtime_;
#ifndef NDEBUG
const std::string name_;
#endif
std::unique_ptr<jsi::Value> function_;
public:
ShareableRemoteFunction(jsi::Runtime &rt, jsi::Function &&function)
: Shareable(RemoteFunctionType),
runtime_(&rt),
#ifndef NDEBUG
name_(function.getProperty(rt, "name").asString(rt).utf8(rt)),
#endif
function_(std::make_unique<jsi::Value>(rt, std::move(function))) {
}
~ShareableRemoteFunction() {
cleanupIfRuntimeExists(runtime_, function_);
}
jsi::Value toJSValue(jsi::Runtime &rt) override;
};
class ShareableHandle : public Shareable {
private:
// We don't release the initializer since the handle can get
// initialized in parallel on multiple threads. However this is not a problem,
// since the final value is taken from a cache on the runtime which guarantees
// sequential access.
std::unique_ptr<ShareableObject> initializer_;
std::unique_ptr<jsi::Value> remoteValue_;
mutable std::mutex initializationMutex_;
jsi::Runtime *remoteRuntime_;
public:
ShareableHandle(jsi::Runtime &rt, const jsi::Object &initializerObject)
: Shareable(HandleType),
initializer_(std::make_unique<ShareableObject>(rt, initializerObject)) {
}
~ShareableHandle() {
cleanupIfRuntimeExists(remoteRuntime_, remoteValue_);
}
jsi::Value toJSValue(jsi::Runtime &rt) override;
};
class ShareableString : public Shareable {
public:
explicit ShareableString(const std::string &string)
: Shareable(StringType), data_(string) {}
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
const std::string data_;
};
#if REACT_NATIVE_MINOR_VERSION >= 71
class ShareableBigInt : public Shareable {
public:
explicit ShareableBigInt(jsi::Runtime &rt, const jsi::BigInt &bigint)
: Shareable(BigIntType), string_(bigint.toString(rt).utf8(rt)) {}
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
const std::string string_;
};
#endif
class ShareableScalar : public Shareable {
public:
explicit ShareableScalar(double number) : Shareable(NumberType) {
data_.number = number;
}
explicit ShareableScalar(bool boolean) : Shareable(BooleanType) {
data_.boolean = boolean;
}
ShareableScalar() : Shareable(UndefinedType) {}
explicit ShareableScalar(std::nullptr_t) : Shareable(NullType) {}
jsi::Value toJSValue(jsi::Runtime &);
protected:
union Data {
bool boolean;
double number;
};
private:
Data data_;
};
} // namespace reanimated

View File

@@ -0,0 +1,52 @@
#include "AsyncQueue.h"
#include <utility>
namespace reanimated {
AsyncQueue::AsyncQueue(std::string name)
: state_(std::make_shared<AsyncQueueState>()) {
auto thread = std::thread([name, state = state_] {
#if __APPLE__
pthread_setname_np(name.c_str());
#endif
while (state->running) {
std::unique_lock<std::mutex> lock(state->mutex);
state->cv.wait(
lock, [state] { return !state->queue.empty() || !state->running; });
if (!state->running) {
return;
}
if (state->queue.empty()) {
continue;
}
auto job = std::move(state->queue.front());
state->queue.pop();
lock.unlock();
job();
}
});
#ifdef ANDROID
pthread_setname_np(thread.native_handle(), name.c_str());
#endif
thread.detach();
}
AsyncQueue::~AsyncQueue() {
{
std::unique_lock<std::mutex> lock(state_->mutex);
state_->running = false;
state_->queue = {};
}
state_->cv.notify_all();
}
void AsyncQueue::push(std::function<void()> &&job) {
{
std::unique_lock<std::mutex> lock(state_->mutex);
state_->queue.emplace(job);
}
state_->cv.notify_one();
}
} // namespace reanimated

View File

@@ -0,0 +1,35 @@
#pragma once
#include <jsi/jsi.h>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <queue>
#include <string>
#include <thread>
#include <utility>
#include <vector>
namespace reanimated {
struct AsyncQueueState {
std::atomic_bool running{true};
std::mutex mutex;
std::condition_variable cv;
std::queue<std::function<void()>> queue;
};
class AsyncQueue {
public:
explicit AsyncQueue(std::string name);
~AsyncQueue();
void push(std::function<void()> &&job);
private:
const std::shared_ptr<AsyncQueueState> state_;
};
} // namespace reanimated

View File

@@ -0,0 +1,14 @@
#pragma once
#include <algorithm>
namespace reanimated {
namespace collection {
template <class CollectionType, class ValueType>
inline bool contains(const CollectionType &collection, const ValueType &value) {
return collection.find(value) != collection.end();
}
} // namespace collection
} // namespace reanimated

View File

@@ -0,0 +1,5 @@
#include "FeaturesConfig.h"
namespace reanimated {
bool FeaturesConfig::_isLayoutAnimationEnabled = false;
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include <string>
namespace reanimated {
class FeaturesConfig {
public:
static inline bool isLayoutAnimationEnabled() {
return _isLayoutAnimationEnabled;
}
static inline void setLayoutAnimationEnabled(bool isLayoutAnimationEnabled) {
_isLayoutAnimationEnabled = isLayoutAnimationEnabled;
}
private:
static bool _isLayoutAnimationEnabled;
};
} // namespace reanimated

View File

@@ -0,0 +1,340 @@
#include "JSISerializer.h"
#include <cxxabi.h>
#include <iostream>
#include <sstream>
const std::vector<std::string> SUPPORTED_ERROR_TYPES = {
"Error",
"AggregateError",
"EvalError",
"RangeError",
"ReferenceError",
"SyntaxError",
"TypeError",
"URIError",
"InternalError"};
const std::vector<std::string> SUPPORTED_INDEXED_COLLECTION_TYPES = {
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"BigInt64Array",
"BigUint64Array",
"Float32Array",
"Float64Array",
};
const std::vector<std::string> SUPPORTED_STRUCTURED_DATA_TYPES = {
"ArrayBuffer",
"SharedArrayBuffer",
"DataView",
"Atomics",
"JSON",
};
const std::vector<std::string> SUPPORTED_MANAGING_MEMORY_TYPES = {
"WeakRef",
"FinalizationRegistry",
};
const std::vector<std::string> SUPPORTED_ABSTRACTION_OBJECT_TYPES = {
"Iterator",
"AsyncIterator",
"Promise",
"GeneratorFunction",
"AsyncGeneratorFunction",
"Generator",
"AsyncGenerator",
"AsyncFunction",
};
const std::vector<std::string> SUPPORTED_REFLECTION_TYPES = {
"Reflect",
"Proxy",
};
static inline std::string getObjectTypeName(
jsi::Runtime &rt,
const jsi::Object &object) {
return object.getPropertyAsObject(rt, "constructor")
.getProperty(rt, "name")
.toString(rt)
.utf8(rt);
}
static inline bool isInstanceOf(
jsi::Runtime &rt,
const jsi::Object &object,
const std::string &type) {
return getObjectTypeName(rt, object) == type;
}
static inline bool isInstanceOfAny(
jsi::Runtime &rt,
const jsi::Object &object,
const std::vector<std::string> &supportedTypes) {
auto instanceType = getObjectTypeName(rt, object);
return std::find(
supportedTypes.begin(), supportedTypes.end(), instanceType) !=
supportedTypes.end();
}
JSISerializer::JSISerializer(jsi::Runtime &rt)
: rt_(rt),
visitedNodes_(rt_.global()
.getPropertyAsFunction(rt_, "Set")
.callAsConstructor(rt_)
.asObject(rt_)) {}
std::string JSISerializer::stringifyWithName(const jsi::Object &object) {
std::stringstream ss;
ss << '[' << getObjectTypeName(rt_, object) << ']';
return ss.str();
}
std::string JSISerializer::stringifyArray(const jsi::Array &arr) {
std::stringstream ss;
ss << '[';
for (size_t i = 0, length = arr.size(rt_); i < length; i++) {
jsi::Value element = arr.getValueAtIndex(rt_, i);
ss << stringifyJSIValueRecursively(element);
if (i != length - 1) {
ss << ", ";
}
}
ss << ']';
return ss.str();
}
std::string JSISerializer::stringifyFunction(const jsi::Function &func) {
std::stringstream ss;
auto kind = (func.isHostFunction(rt_) ? "jsi::HostFunction" : "Function");
auto name = func.getProperty(rt_, "name").toString(rt_).utf8(rt_);
name = name.empty() ? "anonymous" : name;
ss << '[' << kind << ' ' << name << ']';
return ss.str();
}
std::string JSISerializer::stringifyHostObject(jsi::HostObject &hostObject) {
int status = -1;
char *hostObjClassName =
abi::__cxa_demangle(typeid(hostObject).name(), NULL, NULL, &status);
if (status != 0) {
return "[jsi::HostObject]";
}
std::stringstream ss;
ss << "[jsi::HostObject(" << hostObjClassName << ")";
std::free(hostObjClassName);
auto props = hostObject.getPropertyNames(rt_);
auto propsCount = props.size();
if (propsCount > 0) {
ss << " {";
auto lastKey = props.back().utf8(rt_);
for (const auto &key : props) {
auto formattedKey = key.utf8(rt_);
auto value = hostObject.get(rt_, key);
ss << '"' << formattedKey << '"' << ": "
<< stringifyJSIValueRecursively(value);
if (formattedKey != lastKey) {
ss << ", ";
}
}
ss << '}';
}
ss << ']';
return ss.str();
}
std::string JSISerializer::stringifyObject(const jsi::Object &object) {
std::stringstream ss;
ss << '{';
auto props = object.getPropertyNames(rt_);
for (size_t i = 0, propsCount = props.size(rt_); i < propsCount; i++) {
jsi::String propName = props.getValueAtIndex(rt_, i).toString(rt_);
ss << '"' << propName.utf8(rt_) << '"' << ": "
<< stringifyJSIValueRecursively(object.getProperty(rt_, propName));
if (i != propsCount - 1) {
ss << ", ";
}
}
ss << '}';
return ss.str();
}
std::string JSISerializer::stringifyError(const jsi::Object &object) {
std::stringstream ss;
ss << '[' << object.getProperty(rt_, "name").toString(rt_).utf8(rt_) << ": "
<< object.getProperty(rt_, "message").toString(rt_).utf8(rt_) << ']';
return ss.str();
}
std::string JSISerializer::stringifySet(const jsi::Object &object) {
std::stringstream ss;
jsi::Function arrayFrom = rt_.global()
.getPropertyAsObject(rt_, "Array")
.getPropertyAsFunction(rt_, "from");
jsi::Object result = arrayFrom.call(rt_, object).asObject(rt_);
if (!result.isArray(rt_)) {
return "[Set]";
}
auto arr = result.asArray(rt_);
ss << "Set {";
for (size_t i = 0, length = arr.size(rt_); i < length; i++) {
ss << stringifyJSIValueRecursively(arr.getValueAtIndex(rt_, i));
if (i != length - 1) {
ss << ", ";
}
}
ss << '}';
return ss.str();
}
std::string JSISerializer::stringifyMap(const jsi::Object &object) {
std::stringstream ss;
jsi::Function arrayFrom = rt_.global()
.getPropertyAsObject(rt_, "Array")
.getPropertyAsFunction(rt_, "from");
jsi::Object result = arrayFrom.call(rt_, object).asObject(rt_);
if (!result.isArray(rt_)) {
return "[Map]";
}
auto arr = result.asArray(rt_);
ss << "Map {";
for (size_t i = 0, length = arr.size(rt_); i < length; i++) {
auto pair = arr.getValueAtIndex(rt_, i).asObject(rt_).getArray(rt_);
auto key = pair.getValueAtIndex(rt_, 0);
auto value = pair.getValueAtIndex(rt_, 1);
ss << stringifyJSIValueRecursively(key) << ": "
<< stringifyJSIValueRecursively(value);
if (i != length - 1) {
ss << ", ";
}
}
ss << '}';
return ss.str();
}
std::string JSISerializer::stringifyRecursiveType(const jsi::Object &object) {
auto type = getObjectTypeName(rt_, object);
if (type == "Array") {
return "[...]";
}
if (type == "Object") {
return "{...}";
}
return "...";
}
std::string JSISerializer::stringifyWithToString(const jsi::Object &object) {
return object.getPropertyAsFunction(rt_, "toString")
.callWithThis(rt_, object)
.toString(rt_)
.utf8(rt_);
}
std::string JSISerializer::stringifyJSIValueRecursively(
const jsi::Value &value,
bool isTopLevel) {
if (value.isBool() || value.isNumber()) {
return value.toString(rt_).utf8(rt_);
}
if (value.isString()) {
return isTopLevel ? value.getString(rt_).utf8(rt_)
: '"' + value.getString(rt_).utf8(rt_) + '"';
}
if (value.isSymbol()) {
return value.getSymbol(rt_).toString(rt_);
}
#if REACT_NATIVE_MINOR_VERSION >= 71
if (value.isBigInt()) {
return value.getBigInt(rt_).toString(rt_).utf8(rt_) + 'n';
}
#endif
if (value.isUndefined()) {
return "undefined";
}
if (value.isNull()) {
return "null";
}
if (value.isObject()) {
jsi::Object object = value.asObject(rt_);
if (hasBeenVisited(object)) {
return stringifyRecursiveType(object);
}
markAsVisited(object);
if (object.isArray(rt_)) {
return stringifyArray(object.getArray(rt_));
}
if (object.isFunction(rt_)) {
return stringifyFunction(object.getFunction(rt_));
}
if (object.isHostObject(rt_)) {
return stringifyHostObject(*object.getHostObject(rt_));
}
if (isInstanceOfAny(rt_, object, SUPPORTED_ERROR_TYPES)) {
return stringifyError(object);
}
if (isInstanceOfAny(rt_, object, SUPPORTED_INDEXED_COLLECTION_TYPES) ||
isInstanceOfAny(rt_, object, SUPPORTED_STRUCTURED_DATA_TYPES) ||
isInstanceOfAny(rt_, object, SUPPORTED_MANAGING_MEMORY_TYPES) ||
isInstanceOfAny(rt_, object, SUPPORTED_ABSTRACTION_OBJECT_TYPES) ||
isInstanceOfAny(rt_, object, SUPPORTED_REFLECTION_TYPES) ||
isInstanceOf(rt_, object, "Intl") ||
isInstanceOf(rt_, object, "WeakMap") ||
isInstanceOf(rt_, object, "WeakSet")) {
// TODO: Consider extending this log info
return stringifyWithName(object);
}
if (isInstanceOf(rt_, object, "Date") ||
isInstanceOf(rt_, object, "RegExp")) {
return stringifyWithToString(object);
}
if (isInstanceOf(rt_, object, "Map")) {
return stringifyMap(object);
}
if (isInstanceOf(rt_, object, "Set")) {
return stringifySet(object);
}
return stringifyObject(object);
}
throw std::runtime_error("[Reanimated] Unsupported value type.");
}
std::string stringifyJSIValue(jsi::Runtime &rt, const jsi::Value &value) {
JSISerializer serializer(rt);
return serializer.stringifyJSIValueRecursively(value, true);
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include <jsi/jsi.h>
#include <string>
#include <vector>
using namespace facebook;
namespace {
class JSISerializer {
public:
explicit JSISerializer(jsi::Runtime &rt);
std::string stringifyJSIValueRecursively(
const jsi::Value &value,
bool isTopLevel = false);
private:
std::string stringifyArray(const jsi::Array &arr);
std::string stringifyFunction(const jsi::Function &func);
std::string stringifyHostObject(jsi::HostObject &hostObject);
std::string stringifyObject(const jsi::Object &object);
std::string stringifyError(const jsi::Object &object);
std::string stringifySet(const jsi::Object &object);
std::string stringifyMap(const jsi::Object &object);
std::string stringifyWithName(const jsi::Object &object);
std::string stringifyWithToString(const jsi::Object &object);
std::string stringifyRecursiveType(const jsi::Object &object);
bool hasBeenVisited(const jsi::Object &object) {
return visitedNodes_.getPropertyAsFunction(rt_, "has")
.callWithThis(rt_, visitedNodes_, object)
.getBool();
}
void markAsVisited(const jsi::Object &object) {
visitedNodes_.getPropertyAsFunction(rt_, "add")
.callWithThis(rt_, visitedNodes_, object);
}
jsi::Runtime &rt_;
jsi::Object visitedNodes_;
};
} // namespace
std::string stringifyJSIValue(jsi::Runtime &rt, const jsi::Value &value);

View File

@@ -0,0 +1,16 @@
#include "JSLogger.h"
#include <memory>
namespace reanimated {
void JSLogger::warnOnJS(const std::string &warning) const {
#ifndef NDEBUG
jsScheduler_->scheduleOnJS([warning](jsi::Runtime &rt) {
auto console = rt.global().getPropertyAsObject(rt, "console");
auto warn = console.getPropertyAsFunction(rt, "warn");
warn.call(rt, jsi::String::createFromUtf8(rt, warning));
});
#endif // NDEBUG
}
} // namespace reanimated

View File

@@ -0,0 +1,20 @@
#pragma once
#include "JSScheduler.h"
#include <memory>
#include <string>
namespace reanimated {
class JSLogger {
public:
explicit JSLogger(const std::shared_ptr<JSScheduler> &jsScheduler)
: jsScheduler_(jsScheduler) {}
void warnOnJS(const std::string &warning) const;
private:
const std::shared_ptr<JSScheduler> jsScheduler_;
};
} // namespace reanimated

View File

@@ -0,0 +1,36 @@
#include "JSScheduler.h"
using namespace facebook;
using namespace react;
namespace reanimated {
JSScheduler::JSScheduler(
jsi::Runtime &rnRuntime,
const std::shared_ptr<CallInvoker> &jsCallInvoker)
: scheduleOnJS([&](Job job) {
jsCallInvoker_->invokeAsync(
[job = std::move(job), &rt = rnRuntime_] { job(rt); });
}),
rnRuntime_(rnRuntime),
jsCallInvoker_(jsCallInvoker) {}
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
// With `runtimeExecutor`.
JSScheduler::JSScheduler(
jsi::Runtime &rnRuntime,
RuntimeExecutor runtimeExecutor)
: scheduleOnJS([&](Job job) {
runtimeExecutor_(
[job = std::move(job)](jsi::Runtime &runtime) { job(runtime); });
}),
rnRuntime_(rnRuntime),
runtimeExecutor_(runtimeExecutor) {}
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED
const std::shared_ptr<CallInvoker> JSScheduler::getJSCallInvoker() const {
assert(
jsCallInvoker_ != nullptr &&
"[Reanimated] Expected jsCallInvoker, got nullptr instead.");
return jsCallInvoker_;
}
} // namespace reanimated

View File

@@ -0,0 +1,42 @@
#pragma once
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/RuntimeExecutor.h>
#include <jsi/jsi.h>
#include <memory>
#include <utility>
using namespace facebook;
using namespace react;
using Job = std::function<void(jsi::Runtime &rt)>;
namespace reanimated {
class JSScheduler {
public:
// With `jsCallInvoker`.
explicit JSScheduler(
jsi::Runtime &rnRuntime,
const std::shared_ptr<CallInvoker> &jsCallInvoker);
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
// With `runtimeExecutor`.
explicit JSScheduler(
jsi::Runtime &rnRuntime,
RuntimeExecutor runtimeExecutor);
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED
const std::function<void(Job)> scheduleOnJS = nullptr;
const std::shared_ptr<CallInvoker> getJSCallInvoker() const;
protected:
jsi::Runtime &rnRuntime_;
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
RuntimeExecutor runtimeExecutor_ = nullptr;
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED
const std::shared_ptr<CallInvoker> jsCallInvoker_ = nullptr;
};
} // namespace reanimated

View File

@@ -0,0 +1,102 @@
#pragma once
#include <jsi/jsi.h>
#ifdef RCT_NEW_ARCH_ENABLED
#include <react/renderer/core/ReactPrimitives.h>
#endif
#include <string>
#include <utility>
#include <vector>
using namespace facebook;
#ifdef RCT_NEW_ARCH_ENABLED
using namespace react;
#endif
namespace reanimated {
#ifdef RCT_NEW_ARCH_ENABLED
using SynchronouslyUpdateUIPropsFunction =
std::function<void(jsi::Runtime &rt, Tag tag, const jsi::Object &props)>;
using UpdatePropsFunction =
std::function<void(jsi::Runtime &rt, const jsi::Value &operations)>;
using RemoveFromPropsRegistryFunction =
std::function<void(jsi::Runtime &rt, const jsi::Value &viewTags)>;
using ObtainPropFunction = std::function<jsi::Value(
jsi::Runtime &rt,
const jsi::Value &shadowNodeWrapper,
const jsi::Value &propName)>;
using DispatchCommandFunction = std::function<void(
jsi::Runtime &rt,
const jsi::Value &shadowNodeValue,
const jsi::Value &commandNameValue,
const jsi::Value &argsValue)>;
using MeasureFunction = std::function<
jsi::Value(jsi::Runtime &rt, const jsi::Value &shadowNodeValue)>;
#else
using UpdatePropsFunction =
std::function<void(jsi::Runtime &rt, const jsi::Value &operations)>;
using ScrollToFunction = std::function<void(int, double, double, bool)>;
using DispatchCommandFunction = std::function<void(
jsi::Runtime &rt,
const int viewTag,
const jsi::Value &commandNameValue,
const jsi::Value &argsValue)>;
using MeasureFunction =
std::function<std::vector<std::pair<std::string, double>>(int)>;
using ObtainPropFunction =
std::function<jsi::Value(jsi::Runtime &, const int, const jsi::Value &)>;
#endif // RCT_NEW_ARCH_ENABLED
using RequestRenderFunction =
std::function<void(std::function<void(const double)>, jsi::Runtime &)>;
using GetAnimationTimestampFunction = std::function<double(void)>;
using ProgressLayoutAnimationFunction =
std::function<void(jsi::Runtime &, int, jsi::Object, bool)>;
using EndLayoutAnimationFunction = std::function<void(int, bool)>;
using RegisterSensorFunction =
std::function<int(int, int, int, std::function<void(double[], int)>)>;
using UnregisterSensorFunction = std::function<void(int)>;
using SetGestureStateFunction = std::function<void(int, int)>;
using ConfigurePropsFunction = std::function<void(
jsi::Runtime &rt,
const jsi::Value &uiProps,
const jsi::Value &nativeProps)>;
using KeyboardEventSubscribeFunction =
std::function<int(std::function<void(int, int)>, bool)>;
using KeyboardEventUnsubscribeFunction = std::function<void(int)>;
using MaybeFlushUIUpdatesQueueFunction = std::function<void()>;
struct PlatformDepMethodsHolder {
RequestRenderFunction requestRender;
#ifdef RCT_NEW_ARCH_ENABLED
SynchronouslyUpdateUIPropsFunction synchronouslyUpdateUIPropsFunction;
#else
UpdatePropsFunction updatePropsFunction;
ScrollToFunction scrollToFunction;
DispatchCommandFunction dispatchCommandFunction;
MeasureFunction measureFunction;
ConfigurePropsFunction configurePropsFunction;
ObtainPropFunction obtainPropFunction;
#endif
GetAnimationTimestampFunction getAnimationTimestamp;
ProgressLayoutAnimationFunction progressLayoutAnimation;
EndLayoutAnimationFunction endLayoutAnimation;
RegisterSensorFunction registerSensor;
UnregisterSensorFunction unregisterSensor;
SetGestureStateFunction setGestureStateFunction;
KeyboardEventSubscribeFunction subscribeForKeyboardEvents;
KeyboardEventUnsubscribeFunction unsubscribeFromKeyboardEvents;
MaybeFlushUIUpdatesQueueFunction maybeFlushUIUpdatesQueueFunction;
};
} // namespace reanimated

View File

@@ -0,0 +1,11 @@
#pragma once
#ifdef ANDROID
#include "Logger.h"
#include "LoggerInterface.h"
#include "SpeedChecker.h"
#else
#include "Common/cpp/hidden_headers/Logger.h"
#include "Common/cpp/hidden_headers/LoggerInterface.h"
#include "Common/cpp/hidden_headers/SpeedChecker.h"
#endif

View File

@@ -0,0 +1,26 @@
#include "ReanimatedJSIUtils.h"
#include <vector>
using namespace facebook;
namespace reanimated::jsi_utils {
jsi::Array convertStringToArray(
jsi::Runtime &rt,
const std::string &value,
const unsigned int expectedSize) {
std::vector<float> transformMatrixList;
std::istringstream stringStream(value);
std::copy(
std::istream_iterator<float>(stringStream),
std::istream_iterator<float>(),
std::back_inserter(transformMatrixList));
assert(transformMatrixList.size() == expectedSize);
jsi::Array matrix(rt, expectedSize);
for (unsigned int i = 0; i < expectedSize; i++) {
matrix.setValueAtIndex(rt, i, transformMatrixList[i]);
}
return matrix;
}
} // namespace reanimated::jsi_utils

View File

@@ -0,0 +1,176 @@
#pragma once
#include <jsi/jsi.h>
#include <sstream>
#include <string>
#include <tuple>
#include <utility>
using namespace facebook;
namespace reanimated {
namespace jsi_utils {
// `get` functions take a pointer to `jsi::Value` and
// call an appropriate method to cast to the native type
template <typename T>
inline T get(jsi::Runtime &rt, const jsi::Value *value);
template <>
inline double get<double>(jsi::Runtime &, const jsi::Value *value) {
return value->asNumber();
}
template <>
inline int get<int>(jsi::Runtime &, const jsi::Value *value) {
return value->asNumber();
}
template <>
inline bool get<bool>(jsi::Runtime &, const jsi::Value *value) {
if (!value->isBool()) {
throw jsi::JSINativeException("[Reanimated] Expected a boolean.");
}
return value->getBool();
}
template <>
inline jsi::Object get<jsi::Object>(jsi::Runtime &rt, const jsi::Value *value) {
return value->asObject(rt);
}
template <>
inline jsi::Value const &get<jsi::Value const &>(
jsi::Runtime &,
const jsi::Value *value) {
return *value;
}
// `convertArgs` functions take a variadic template parameter of target (C++)
// argument types `Targs` and a `jsi::Value` array `args`, and converts `args`
// to a tuple of typed C++ arguments to be passed to the native implementation.
// This is accomplished by dispatching (at compile time) to the correct
// implementation based on the first type of `Targs`, using SFINAE to select the
// correct specialization, and concatenating with the result of recursion on the
// rest of `Targs`
// BEGIN implementations for `convertArgs` specializations.
// specialization for empty `Targs` - returns an empty tuple
template <typename... Args>
inline std::enable_if_t<(sizeof...(Args) == 0), std::tuple<>> convertArgs(
jsi::Runtime &,
const jsi::Value *) {
return std::make_tuple();
}
// calls `get<First>` on the first argument to retrieve the native type,
// then calls recursively on the rest of `args`
// and returns the concatenation of results
template <typename T, typename... Rest>
inline std::tuple<T, Rest...> convertArgs(
jsi::Runtime &rt,
const jsi::Value *args) {
auto arg = std::tuple<T>(get<T>(rt, args));
auto rest = convertArgs<Rest...>(rt, std::next(args));
return std::tuple_cat(std::move(arg), std::move(rest));
}
// END implementations for `convertArgs` specializations.
// returns a tuple with the result of casting `args` to appropriate
// native C++ types needed to call `function`
template <typename Ret, typename... Args>
std::tuple<Args...> getArgsForFunction(
std::function<Ret(Args...)>,
jsi::Runtime &rt,
const jsi::Value *args,
const size_t count) {
assert(sizeof...(Args) == count);
return convertArgs<Args...>(rt, args);
}
// returns a tuple with the result of casting `args` to appropriate
// native C++ types needed to call `function`,
// passing `rt` as the first argument
template <typename Ret, typename... Args>
std::tuple<jsi::Runtime &, Args...> getArgsForFunction(
std::function<Ret(jsi::Runtime &, Args...)>,
jsi::Runtime &rt,
const jsi::Value *args,
const size_t count) {
assert(sizeof...(Args) == count);
return std::tuple_cat(std::tie(rt), convertArgs<Args...>(rt, args));
}
// calls `function` with `args`
template <typename Ret, typename... Args>
inline jsi::Value apply(
std::function<Ret(Args...)> function,
std::tuple<Args...> args) {
return std::apply(function, std::move(args));
}
// calls void-returning `function` with `args`,
// and returns `undefined`
template <typename... Args>
inline jsi::Value apply(
std::function<void(Args...)> function,
std::tuple<Args...> args) {
std::apply(function, std::move(args));
return jsi::Value::undefined();
}
// returns a function with JSI calling convention
// from a native function `function`
template <typename Fun>
jsi::HostFunctionType createHostFunction(Fun function) {
return [function](
jsi::Runtime &rt,
const jsi::Value &,
const jsi::Value *args,
const size_t count) {
auto argz = getArgsForFunction(function, rt, args, count);
return apply(function, std::move(argz));
};
}
// used to determine if `function<Ret(Args...)>`
// takes `Runtime &` as its first argument
template <typename... Args>
struct takes_runtime {
static constexpr size_t value = 0;
};
// specialization for `function<Ret(Runtime &, Rest...)`
template <typename... Rest>
struct takes_runtime<jsi::Runtime &, Rest...> {
static constexpr size_t value = 1;
};
// creates a JSI compatible function from `function`
// and installs it as a global function named `name`
// in the `rt` JS runtime
template <typename Ret, typename... Args>
void installJsiFunction(
jsi::Runtime &rt,
std::string_view name,
std::function<Ret(Args...)> function) {
auto clb = createHostFunction(function);
auto argsCount = sizeof...(Args) - takes_runtime<Args...>::value;
jsi::Value jsiFunction = jsi::Function::createFromHostFunction(
rt, jsi::PropNameID::forAscii(rt, name.data()), argsCount, clb);
rt.global().setProperty(rt, name.data(), jsiFunction);
}
// this should take care of passing types convertible to `function`
template <typename Fun>
void installJsiFunction(jsi::Runtime &rt, std::string_view name, Fun function) {
installJsiFunction(rt, name, std::function(std::forward<Fun>(function)));
}
jsi::Array convertStringToArray(
jsi::Runtime &rt,
const std::string &value,
const unsigned int expectedSize);
} // namespace jsi_utils
} // namespace reanimated

View File

@@ -0,0 +1,94 @@
#include "ReanimatedVersion.h"
#include <memory>
#include <regex>
#include <string>
#include "JSLogger.h"
#ifdef REANIMATED_VERSION
#define STRINGIZE(x) #x
#define STRINGIZE2(x) STRINGIZE(x)
#define REANIMATED_VERSION_STRING STRINGIZE2(REANIMATED_VERSION)
#endif // REANIMATED_VERSION
using namespace facebook;
namespace reanimated {
std::string getReanimatedCppVersion() {
return std::string(REANIMATED_VERSION_STRING);
}
void injectReanimatedCppVersion(jsi::Runtime &rnRuntime) {
auto version = getReanimatedCppVersion();
rnRuntime.global().setProperty(
rnRuntime,
"_REANIMATED_VERSION_CPP",
jsi::String::createFromUtf8(rnRuntime, version));
}
#ifndef NDEBUG
// This function is pretty much a copy of
// `src/reanimated2/platform-specific/checkVersion.ts`.
bool matchVersion(const std::string &version1, const std::string &version2) {
std::regex pattern("^\\d+\\.\\d+\\.\\d+$");
if (std::regex_match(version1, pattern) &&
std::regex_match(version2, pattern)) {
auto majorPattern = std::regex("^\\d+");
auto major1 = std::regex_search(version1, majorPattern);
auto major2 = std::regex_search(version2, majorPattern);
if (major1 != major2) {
return false;
}
auto minorPattern = std::regex("\\.\\d+\\.");
auto minor1 = std::regex_search(version1, minorPattern);
auto minor2 = std::regex_search(version2, minorPattern);
if (minor1 != minor2) {
return false;
}
return true;
} else {
return version1 == version2;
}
}
void checkJSVersion(
jsi::Runtime &rnRuntime,
const std::shared_ptr<JSLogger> &jsLogger) {
auto cppVersion = getReanimatedCppVersion();
auto maybeJSVersion =
rnRuntime.global().getProperty(rnRuntime, "_REANIMATED_VERSION_JS");
if (maybeJSVersion.isUndefined()) {
jsLogger->warnOnJS(
std::string(
"[Reanimated] C++ side failed to resolve JavaScript code version\n") +
"See `https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#c-side-failed-to-resolve-javascript-code-version` for more details.");
return;
}
auto jsVersion = maybeJSVersion.asString(rnRuntime).utf8(rnRuntime);
if (!matchVersion(cppVersion, jsVersion)) {
jsLogger->warnOnJS(
std::string(
"[Reanimated] Mismatch between C++ code version and JavaScript code version (") +
cppVersion + " vs. " + jsVersion + " respectively).\n" +
"See `https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#mismatch-between-c-code-version-and-javascript-code-version` for more details.");
return;
}
}
#else
void checkJSVersion(
jsi::Runtime &rnRuntime,
const std::shared_ptr<JSLogger> &jsLogger) {
// In release builds we don't check the version, hence
// this function is a NOOP.
}
bool matchVersion(const std::string &version1, const std::string &version2) {
// Stub implementation for release builds.
return true;
}
#endif // NDEBUG
}; // namespace reanimated

View File

@@ -0,0 +1,17 @@
#pragma once
#include <jsi/jsi.h>
#include <memory>
#include <string>
#include "JSLogger.h"
using namespace facebook;
namespace reanimated {
std::string getReanimatedCppVersion();
void injectReanimatedCppVersion(jsi::Runtime &);
bool matchVersion(const std::string &, const std::string &);
void checkJSVersion(jsi::Runtime &, const std::shared_ptr<JSLogger> &);
}; // namespace reanimated

View File

@@ -0,0 +1,71 @@
#pragma once
#ifndef NDEBUG
#include <cxxabi.h>
#include <atomic>
#include <iostream>
#include <string>
#ifdef ANDROID
#include <android/log.h>
#endif
namespace reanimated {
// This is a class that counts how many instances of a different class there
// are. It is meant only to be used with classes that should only have one
// instance.
template <class T>
class SingleInstanceChecker {
public:
SingleInstanceChecker();
~SingleInstanceChecker();
private:
void assertWithMessage(bool condition, std::string message) {
if (!condition) {
#ifdef ANDROID
__android_log_print(
ANDROID_LOG_WARN, "Reanimated", "%s", message.c_str());
#else
std::cerr << "[Reanimated] " << message << std::endl;
#endif
#ifdef IS_REANIMATED_EXAMPLE_APP
assert(false);
#endif
}
}
// A static field will exist separately for every class template.
// This has to be inline for automatic initialization.
inline static std::atomic<int> instanceCount_;
};
template <class T>
SingleInstanceChecker<T>::SingleInstanceChecker() {
int status = 0;
std::string className =
__cxxabiv1::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status);
// Only one instance should exist, but it is possible for two instances
// to co-exist during a reload.
assertWithMessage(
instanceCount_ <= 1,
"[Reanimated] More than one instance of " + className +
" present. This may indicate a memory leak due to a retain cycle.");
instanceCount_++;
}
template <class T>
SingleInstanceChecker<T>::~SingleInstanceChecker() {
instanceCount_--;
}
} // namespace reanimated
#endif // NDEBUG

View File

@@ -0,0 +1,49 @@
#pragma once
#include <condition_variable>
#include <mutex>
#include <queue>
#include <utility>
namespace reanimated {
//
// Copyright (c) 2013 Juan Palacios juan.palacios.puyana@gmail.com
// Subject to the BSD 2-Clause License
// - see < https://opensource.org/license/bsd-2-clause/ >
//
template <typename T>
class ThreadSafeQueue {
public:
T pop() {
std::unique_lock<std::mutex> mlock(mutex_);
while (queue_.empty()) {
cond_.wait(mlock);
}
const auto item = queue_.front();
queue_.pop();
return item;
}
void push(T &&item) {
std::unique_lock<std::mutex> mlock(mutex_);
queue_.push(std::move(item));
mlock.unlock();
cond_.notify_one();
}
bool empty() const {
std::unique_lock<std::mutex> mlock(mutex_);
const auto res = queue_.empty();
mlock.unlock();
cond_.notify_one();
return res;
}
private:
std::queue<T> queue_;
mutable std::mutex mutex_;
mutable std::condition_variable cond_;
};
} // namespace reanimated

View File

@@ -0,0 +1,70 @@
#include "UIRuntimeDecorator.h"
#include "ReanimatedJSIUtils.h"
namespace reanimated {
void UIRuntimeDecorator::decorate(
jsi::Runtime &uiRuntime,
#ifdef RCT_NEW_ARCH_ENABLED
const RemoveFromPropsRegistryFunction removeFromPropsRegistry,
#else
const ScrollToFunction scrollTo,
#endif
const ObtainPropFunction obtainPropFunction,
const UpdatePropsFunction updateProps,
const MeasureFunction measure,
const DispatchCommandFunction dispatchCommand,
const RequestAnimationFrameFunction requestAnimationFrame,
const GetAnimationTimestampFunction getAnimationTimestamp,
const SetGestureStateFunction setGestureState,
const ProgressLayoutAnimationFunction progressLayoutAnimation,
const EndLayoutAnimationFunction endLayoutAnimation,
const MaybeFlushUIUpdatesQueueFunction maybeFlushUIUpdatesQueue) {
uiRuntime.global().setProperty(uiRuntime, "_UI", true);
#ifdef RCT_NEW_ARCH_ENABLED
jsi_utils::installJsiFunction(uiRuntime, "_updatePropsFabric", updateProps);
jsi_utils::installJsiFunction(
uiRuntime, "_removeFromPropsRegistry", removeFromPropsRegistry);
jsi_utils::installJsiFunction(
uiRuntime, "_dispatchCommandFabric", dispatchCommand);
jsi_utils::installJsiFunction(uiRuntime, "_measureFabric", measure);
#else
jsi_utils::installJsiFunction(uiRuntime, "_updatePropsPaper", updateProps);
jsi_utils::installJsiFunction(
uiRuntime, "_dispatchCommandPaper", dispatchCommand);
jsi_utils::installJsiFunction(uiRuntime, "_scrollToPaper", scrollTo);
jsi_utils::installJsiFunction(
uiRuntime,
"_measurePaper",
[measure](jsi::Runtime &rt, int viewTag) -> jsi::Value {
auto result = measure(viewTag);
jsi::Object resultObject(rt);
for (const auto &item : result) {
resultObject.setProperty(rt, item.first.c_str(), item.second);
}
return resultObject;
});
jsi_utils::installJsiFunction(
uiRuntime, "_obtainPropPaper", obtainPropFunction);
#endif // RCT_NEW_ARCH_ENABLED
jsi_utils::installJsiFunction(
uiRuntime, "requestAnimationFrame", requestAnimationFrame);
jsi_utils::installJsiFunction(
uiRuntime, "_getAnimationTimestamp", getAnimationTimestamp);
jsi_utils::installJsiFunction(
uiRuntime, "_notifyAboutProgress", progressLayoutAnimation);
jsi_utils::installJsiFunction(
uiRuntime, "_notifyAboutEnd", endLayoutAnimation);
jsi_utils::installJsiFunction(uiRuntime, "_setGestureState", setGestureState);
jsi_utils::installJsiFunction(
uiRuntime, "_maybeFlushUIUpdatesQueue", maybeFlushUIUpdatesQueue);
jsi_utils::installJsiFunction(
uiRuntime, "_obtainPropFabric", obtainPropFunction);
}
} // namespace reanimated

View File

@@ -0,0 +1,35 @@
#pragma once
#include <jsi/jsi.h>
#include "PlatformDepMethodsHolder.h"
using namespace facebook;
namespace reanimated {
using RequestAnimationFrameFunction =
std::function<void(jsi::Runtime &, const jsi::Value &)>;
class UIRuntimeDecorator {
public:
static void decorate(
jsi::Runtime &uiRuntime,
#ifdef RCT_NEW_ARCH_ENABLED
const RemoveFromPropsRegistryFunction removeFromPropsRegistry,
#else
const ScrollToFunction scrollTo,
#endif
const ObtainPropFunction obtainPropFunction,
const UpdatePropsFunction updateProps,
const MeasureFunction measure,
const DispatchCommandFunction dispatchCommand,
const RequestAnimationFrameFunction requestAnimationFrame,
const GetAnimationTimestampFunction getAnimationTimestamp,
const SetGestureStateFunction setGestureState,
const ProgressLayoutAnimationFunction progressLayoutAnimation,
const EndLayoutAnimationFunction endLayoutAnimation,
const MaybeFlushUIUpdatesQueueFunction maybeFlushUIUpdatesQueue);
};
} // namespace reanimated

View File

@@ -0,0 +1,20 @@
#include "UIScheduler.h"
#include "ReanimatedRuntime.h"
#include <utility>
namespace reanimated {
void UIScheduler::scheduleOnUI(std::function<void()> job) {
uiJobs_.push(std::move(job));
}
void UIScheduler::triggerUI() {
scheduledOnUI_ = false;
while (!uiJobs_.empty()) {
const auto job = uiJobs_.pop();
job();
}
}
} // namespace reanimated

View File

@@ -0,0 +1,23 @@
#pragma once
#include <ReactCommon/CallInvoker.h>
#include <atomic>
#include <memory>
#include "ThreadSafeQueue.h"
namespace reanimated {
class UIScheduler {
public:
virtual void scheduleOnUI(std::function<void()> job);
virtual void triggerUI();
virtual ~UIScheduler() = default;
protected:
std::atomic<bool> scheduledOnUI_{false};
ThreadSafeQueue<std::function<void()>> uiJobs_;
};
} // namespace reanimated

View File

@@ -0,0 +1,29 @@
#include "WorkletEventHandler.h"
namespace reanimated {
void WorkletEventHandler::process(
const std::shared_ptr<WorkletRuntime> &workletRuntime,
const double eventTimestamp,
const jsi::Value &eventValue) const {
workletRuntime->runGuarded(
handlerFunction_, jsi::Value(eventTimestamp), eventValue);
}
uint64_t WorkletEventHandler::getHandlerId() const {
return handlerId_;
}
const std::string &WorkletEventHandler::getEventName() const {
return eventName_;
}
uint64_t WorkletEventHandler::getEmitterReactTag() const {
return emitterReactTag_;
}
bool WorkletEventHandler::shouldIgnoreEmitterReactTag() const {
return emitterReactTag_ == -1;
}
} // namespace reanimated

View File

@@ -0,0 +1,41 @@
#pragma once
#include <jsi/jsi.h>
#include <memory>
#include <string>
#include <utility>
#include "Shareables.h"
#include "WorkletRuntime.h"
using namespace facebook;
namespace reanimated {
class WorkletEventHandler {
const uint64_t handlerId_;
const uint64_t emitterReactTag_;
const std::string eventName_;
const std::shared_ptr<ShareableWorklet> handlerFunction_;
public:
WorkletEventHandler(
const uint64_t handlerId,
const std::string &eventName,
const uint64_t emitterReactTag,
const std::shared_ptr<ShareableWorklet> &handlerFunction)
: handlerId_(handlerId),
emitterReactTag_(emitterReactTag),
eventName_(eventName),
handlerFunction_(handlerFunction) {}
void process(
const std::shared_ptr<WorkletRuntime> &workletRuntime,
double eventTimestamp,
const jsi::Value &eventValue) const;
uint64_t getHandlerId() const;
const std::string &getEventName() const;
uint64_t getEmitterReactTag() const;
bool shouldIgnoreEmitterReactTag() const;
};
} // namespace reanimated

View File

@@ -0,0 +1,24 @@
#pragma once
#include <memory>
#include <stdexcept>
#include "./LoggerInterface.h"
namespace reanimated {
class Logger {
public:
template <typename T>
static void log(T value) {
if (instance == nullptr) {
throw std::runtime_error("[Reanimated] No logger specified.");
}
instance->log(value);
}
private:
static std::unique_ptr<LoggerInterface> instance;
};
} // namespace reanimated

View File

@@ -0,0 +1,17 @@
#pragma once
#include <string>
namespace reanimated {
class LoggerInterface {
public:
virtual void log(const char *str) = 0;
virtual void log(const std::string &str) = 0;
virtual void log(double d) = 0;
virtual void log(int i) = 0;
virtual void log(bool b) = 0;
virtual ~LoggerInterface() = default;
};
} // namespace reanimated

View File

@@ -0,0 +1,29 @@
#pragma once
#define CHECK_SPEED 1
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "./Logger.h"
namespace reanimated {
class SpeedChecker {
public:
static void checkSpeed(std::string tag, std::function<void()> fun) {
#if CHECK_SPEED
auto start = std::chrono::system_clock::now();
#endif
fun();
#if CHECK_SPEED
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end - start;
tag += " " + std::to_string(elapsed_seconds.count()) + "s";
Logger::log(tag.c_str());
#endif
}
};
} // namespace reanimated