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

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Software Mansion <swmansion.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,51 @@
<img src="https://user-images.githubusercontent.com/16062886/117443145-ff868480-af37-11eb-8680-648bccf0d0ce.png" alt="React Native Reanimated by Software Mansion" width="100%">
### React Native's Animated library reimplemented
> Reanimated 3 is here! Check out our [documentation page](https://docs.swmansion.com/react-native-reanimated/) for more information
React Native Reanimated provides a more comprehensive,
low level abstraction for the Animated library API to be built
on top of and hence allow for much greater flexibility especially when it
comes to gesture based interactions.
### Nightly CI state
[![Build nightly npm package](https://github.com/software-mansion/react-native-reanimated/actions/workflows/build-nightly-npm-package.yml/badge.svg)](https://github.com/software-mansion/react-native-reanimated/actions/workflows/build-nightly-npm-package.yml)
[![Run nightly monorepo test](https://github.com/software-mansion/react-native-reanimated/actions/workflows/build-monorepo-nightly.yml/badge.svg)](https://github.com/software-mansion/react-native-reanimated/actions/workflows/build-monorepo-nightly.yml)
[![Check static framework nightly build](https://github.com/software-mansion/react-native-reanimated/actions/workflows/check-static-framework-nightly.yml/badge.svg)](https://github.com/software-mansion/react-native-reanimated/actions/workflows/check-static-framework-nightly.yml)
[![Check React Native nightly build](https://github.com/software-mansion/react-native-reanimated/actions/workflows/check-react-native-nightly.yml/badge.svg)](https://github.com/software-mansion/react-native-reanimated/actions/workflows/check-react-native-nightly.yml)
[![Check Expo dev-client nightly build](https://github.com/software-mansion/react-native-reanimated/actions/workflows/check-expo-dev-client-nightly.yml/badge.svg)](https://github.com/software-mansion/react-native-reanimated/actions/workflows/check-expo-dev-client-nightly.yml)
[![Check TypeScript nightly build](https://github.com/software-mansion/react-native-reanimated/actions/workflows/check-TS-react-native.yml/badge.svg)](https://github.com/software-mansion/react-native-reanimated/actions/workflows/check-TS-react-native.yml)
[![Test V8 on Android nightly](https://github.com/software-mansion/react-native-reanimated/actions/workflows/build-v8-nightly.yml/badge.svg)](https://github.com/software-mansion/react-native-reanimated/actions/workflows/build-v8-nightly.yml)
[![Test build on Windows nightly](https://github.com/software-mansion/react-native-reanimated/actions/workflows/build-on-windows-nightly.yml/badge.svg)](https://github.com/software-mansion/react-native-reanimated/actions/workflows/build-on-windows-nightly.yml)
[![Validate urls in source code](https://github.com/software-mansion/react-native-reanimated/actions/workflows/detect-broken-urls-nightly.yml/badge.svg)](https://github.com/software-mansion/react-native-reanimated/actions/workflows/detect-broken-urls-nightly.yml)
## Installation
Check out the [installation](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#installation) section of our docs for the detailed installation instructions.
## Fabric
To learn how to use `react-native-reanimated` with Fabric architecture, head over to [Fabric README](README-Fabric.md). Instructions on how to run Fabric Example within this repo can be found in the [FabricExample README](FabricExample/README.md).
## Documentation
Check out our dedicated documentation page for info about this library, API reference and more: [https://docs.swmansion.com/react-native-reanimated/](https://docs.swmansion.com/react-native-reanimated/)
## Examples
The source code for the example (showcase) app is under the [`Example/`](https://github.com/software-mansion/react-native-reanimated/blob/main/Example/) directory.
If you want to play with the API but don't feel like trying it on a real app, you can run the example project. Check Example/ directory README for installation instructions.
## License
Reanimated library is licensed under [The MIT License](LICENSE).
## Credits
This project has been built and is maintained thanks to the support from [Shopify](https://shopify.com), [Expo.io](https://expo.io) and [Software Mansion](https://swmansion.com)
[![shopify](https://avatars1.githubusercontent.com/u/8085?v=3&s=100 'Shopify.com')](https://shopify.com)
[![expo](https://avatars2.githubusercontent.com/u/12504344?v=3&s=100 'Expo.io')](https://expo.io)
[![swm](https://logo.swmansion.com/logo?color=white&variant=desktop&width=150&tag=react-native-reanimated-github 'Software Mansion')](https://swmansion.com)

View File

@@ -0,0 +1,115 @@
require "json"
require_relative './scripts/reanimated_utils'
reanimated_package_json = JSON.parse(File.read(File.join(__dir__, "package.json")))
$config = find_config()
assert_latest_react_native_with_new_architecture($config, reanimated_package_json)
assert_minimal_react_native_version($config)
$new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
is_release = ENV['PRODUCTION'] == '1'
folly_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32 -DREACT_NATIVE_MINOR_VERSION=' + $config[:react_native_minor_version].to_s
folly_compiler_flags = folly_flags + ' ' + '-Wno-comma -Wno-shorten-64-to-32'
boost_compiler_flags = '-Wno-documentation'
fabric_flags = $new_arch_enabled ? '-DRCT_NEW_ARCH_ENABLED' : ''
example_flag = $config[:is_reanimated_example_app] ? '-DIS_REANIMATED_EXAMPLE_APP' : ''
version_flag = '-DREANIMATED_VERSION=' + reanimated_package_json["version"]
debug_flag = is_release ? '-DNDEBUG' : ''
ios_min_version = $config[:react_native_minor_version] >= 73 ? '13.4' : '9.0'
def self.install_modules_dependencies_legacy(s)
using_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == '1'
s.dependency "React-Core"
if $new_arch_enabled
s.dependency "React-RCTFabric"
s.dependency "React-Codegen"
end
s.dependency "RCT-Folly"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency 'FBLazyVector'
if $config[:react_native_minor_version] <= 71
s.dependency 'FBReactNativeSpec'
end
s.dependency 'React-Core'
s.dependency 'React-CoreModules'
s.dependency 'React-Core/DevSupport'
if !$config[:is_tvos_target]
s.dependency 'React-RCTActionSheet'
end
s.dependency 'React-RCTNetwork'
s.dependency 'React-RCTAnimation'
s.dependency 'React-RCTLinking'
s.dependency 'React-RCTBlob'
s.dependency 'React-RCTSettings'
s.dependency 'React-RCTText'
s.dependency 'React-RCTImage'
s.dependency 'React-Core/RCTWebSocket'
s.dependency 'React-cxxreact'
s.dependency 'React-jsi'
s.dependency 'React-jsiexecutor'
s.dependency 'React-jsinspector'
s.dependency 'Yoga'
s.dependency 'DoubleConversion'
s.dependency 'glog'
if using_hermes && !$config[:is_tvos_target] && $config[:react_native_minor_version] >= 70
s.dependency 'React-hermes'
s.dependency 'hermes-engine'
end
s.dependency 'React-callinvoker'
if $config[:react_native_minor_version] >= 72 && !$new_arch_enabled
s.dependency 'React-RCTAppDelegate'
end
end
Pod::Spec.new do |s|
s.name = "RNReanimated"
s.version = reanimated_package_json["version"]
s.summary = reanimated_package_json["description"]
s.description = <<-DESC
RNReanimated
DESC
s.homepage = "https://github.com/software-mansion/react-native-reanimated"
s.license = "MIT"
s.author = { "author" => "author@domain.cn" }
s.platforms = { :ios => ios_min_version, :tvos => "9.0", :osx => "10.14", :visionos => "1.0" }
s.source = { :git => "https://github.com/software-mansion/react-native-reanimated.git", :tag => "#{s.version}" }
s.source_files = [
"apple/**/*.{mm,h,m}",
"Common/cpp/**/*.{cpp,h}"
]
s.preserve_paths = [
"Common/cpp/hidden_headers/**"
]
gcc_debug_definitions = "$(inherited)"
if $config[:react_native_minor_version] >= 73 || !is_release
gcc_debug_definitions << " HERMES_ENABLE_DEBUGGER=1"
end
s.pod_target_xcconfig = {
"USE_HEADERMAP" => "YES",
"DEFINES_MODULE" => "YES",
"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\" \"$(PODS_TARGET_SRCROOT)\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/boost-for-react-native\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/Headers/Private/React-Core\" \"$(PODS_ROOT)/Headers/Private/Yoga\"",
"FRAMEWORK_SEARCH_PATHS" => "\"${PODS_CONFIGURATION_BUILD_DIR}/React-hermes\"",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
"GCC_PREPROCESSOR_DEFINITIONS[config=Debug]" => gcc_debug_definitions,
"GCC_PREPROCESSOR_DEFINITIONS[config=Release]" => "$(inherited) NDEBUG=1",
}
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
s.xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/boost-for-react-native\" \"$(PODS_ROOT)/glog\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/Headers/Public/React-hermes\" \"$(PODS_ROOT)/Headers/Public/hermes-engine\" \"$(PODS_ROOT)/#{$config[:react_native_common_dir]}\"",
"OTHER_CFLAGS" => "$(inherited)" + " " + folly_flags + " " + fabric_flags + " " + example_flag + " " + version_flag + " " + debug_flag
}
s.requires_arc = true
s.dependency "ReactCommon/turbomodule/core"
if defined?(install_modules_dependencies()) != nil
install_modules_dependencies(s)
else
install_modules_dependencies_legacy(s)
end
end

View File

@@ -0,0 +1,319 @@
project(Reanimated)
cmake_minimum_required(VERSION 3.8)
set (CMAKE_VERBOSE_MAKEFILE ON)
if(${REACT_NATIVE_MINOR_VERSION} GREATER_EQUAL 73)
set (CMAKE_CXX_STANDARD 20)
else()
set (CMAKE_CXX_STANDARD 17)
endif()
# default CMAKE_CXX_FLAGS: "-g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -fstack-protector-all"
if(${REACT_NATIVE_MINOR_VERSION} GREATER_EQUAL 71)
include("${REACT_NATIVE_DIR}/ReactAndroid/cmake-utils/folly-flags.cmake")
add_compile_options(${folly_FLAGS})
else()
set(folly_FLAGS "-DFOLLY_NO_CONFIG=1 -DFOLLY_HAVE_CLOCK_GETTIME=1 -DFOLLY_HAVE_MEMRCHR=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_MOBILE=1 -DFOLLY_HAVE_RECVMMSG=1 -DFOLLY_HAVE_PTHREAD=1")
string(APPEND CMAKE_CXX_FLAGS " ${folly_FLAGS}")
endif()
string(APPEND CMAKE_CXX_FLAGS " -DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION} -DREANIMATED_VERSION=${REANIMATED_VERSION} -DHERMES_ENABLE_DEBUGGER=${HERMES_ENABLE_DEBUGGER}")
string(APPEND CMAKE_CXX_FLAGS " -fexceptions -fno-omit-frame-pointer -frtti -fstack-protector-all -std=c++${CMAKE_CXX_STANDARD} -Wall -Werror")
if(${IS_NEW_ARCHITECTURE_ENABLED})
string(APPEND CMAKE_CXX_FLAGS " -DRCT_NEW_ARCH_ENABLED")
endif()
if(${IS_REANIMATED_EXAMPLE_APP})
string(APPEND CMAKE_CXX_FLAGS " -DIS_REANIMATED_EXAMPLE_APP")
endif()
if(NOT ${CMAKE_BUILD_TYPE} MATCHES "Debug")
string(APPEND CMAKE_CXX_FLAGS " -DNDEBUG")
endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")
set (PACKAGE_NAME "reanimated")
set (BUILD_DIR ${CMAKE_SOURCE_DIR}/build)
set (SRC_DIR ${CMAKE_SOURCE_DIR}/src)
set (COMMON_SRC_DIR "${CMAKE_SOURCE_DIR}/../Common")
file(GLOB_RECURSE SOURCES_COMMON CONFIGURE_DEPENDS "${COMMON_SRC_DIR}/cpp/**.cpp")
file(GLOB_RECURSE SOURCES_ANDROID CONFIGURE_DEPENDS "${SRC_DIR}/main/cpp/**.cpp")
if(${REACT_NATIVE_MINOR_VERSION} GREATER_EQUAL 71)
# Consume shared libraries and headers from prefabs
find_package(fbjni REQUIRED CONFIG)
find_package(ReactAndroid REQUIRED CONFIG)
if(${JS_RUNTIME} STREQUAL "hermes")
find_package(hermes-engine REQUIRED CONFIG)
endif()
else()
# Consume shared libraries from found .so files
if(${IS_NEW_ARCHITECTURE_ENABLED})
message(FATAL_ERROR "not supported")
else()
set (RN_SO_DIR "${REACT_NATIVE_DIR}/ReactAndroid/src/main/jni/first-party/react/jni")
set (FBJNI_HEADERS_DIR "${REACT_NATIVE_DIR}/ReactAndroid/src/main/jni/first-party/fbjni/headers")
endif()
file (GLOB LIBRN_DIR "${RN_SO_DIR}/${ANDROID_ABI}")
endif()
add_library(
${PACKAGE_NAME}
SHARED
${SOURCES_COMMON}
${SOURCES_ANDROID}
${INCLUDE_JSI_CPP}
${INCLUDE_JSIDYNAMIC_CPP}
)
# includes
target_include_directories(
${PACKAGE_NAME}
PRIVATE
"${COMMON_SRC_DIR}/cpp/AnimatedSensor"
"${COMMON_SRC_DIR}/cpp/Fabric"
"${COMMON_SRC_DIR}/cpp/hidden_headers"
"${COMMON_SRC_DIR}/cpp/LayoutAnimations"
"${COMMON_SRC_DIR}/cpp/NativeModules"
"${COMMON_SRC_DIR}/cpp/ReanimatedRuntime"
"${COMMON_SRC_DIR}/cpp/Registries"
"${COMMON_SRC_DIR}/cpp/SharedItems"
"${COMMON_SRC_DIR}/cpp/Tools"
"${SRC_DIR}/main/cpp"
)
if(${REACT_NATIVE_MINOR_VERSION} GREATER_EQUAL 71)
target_include_directories(
${PACKAGE_NAME}
PRIVATE
"${REACT_NATIVE_DIR}/ReactAndroid/src/main/jni/react/turbomodule"
"${REACT_NATIVE_DIR}/ReactCommon"
"${REACT_NATIVE_DIR}/ReactCommon/callinvoker"
"${REACT_NATIVE_DIR}/ReactCommon/react/renderer/graphics/platform/cxx"
"${REACT_NATIVE_DIR}/ReactCommon/runtimeexecutor"
"${REACT_NATIVE_DIR}/ReactCommon/yoga"
)
else()
file(GLOB LIBFBJNI_INCLUDE_DIR ${FBJNI_HEADERS_DIR})
target_include_directories(
${PACKAGE_NAME}
PRIVATE
"${LIBFBJNI_INCLUDE_DIR}"
"${BUILD_DIR}/third-party-ndk/boost/boost_${BOOST_VERSION}"
"${BUILD_DIR}/third-party-ndk/double-conversion"
"${BUILD_DIR}/third-party-ndk/folly"
"${BUILD_DIR}/third-party-ndk/glog/exported"
"${REACT_NATIVE_DIR}/React"
"${REACT_NATIVE_DIR}/React/Base"
"${REACT_NATIVE_DIR}/ReactAndroid/src/main/jni"
"${REACT_NATIVE_DIR}/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni"
"${REACT_NATIVE_DIR}/ReactAndroid/src/main/java/com/facebook/react/fabric/jni"
"${REACT_NATIVE_DIR}/ReactCommon"
"${REACT_NATIVE_DIR}/ReactCommon/callinvoker"
"${REACT_NATIVE_DIR}/ReactCommon/jsi"
"${REACT_NATIVE_DIR}/ReactCommon/hermes"
"${REACT_NATIVE_DIR}/ReactCommon/react/renderer/graphics/platform/cxx"
"${REACT_NATIVE_DIR}/ReactCommon/runtimeexecutor"
"${REACT_NATIVE_DIR}/ReactCommon/turbomodule/core"
"${REACT_NATIVE_DIR}/ReactCommon/turbomodule"
"${REACT_NATIVE_DIR}/ReactCommon/yoga"
)
endif()
# build shared lib
set_target_properties(${PACKAGE_NAME} PROPERTIES LINKER_LANGUAGE CXX)
target_link_libraries(
${PACKAGE_NAME}
log
android
)
if(${REACT_NATIVE_MINOR_VERSION} GREATER_EQUAL 71)
target_link_libraries(
${PACKAGE_NAME}
ReactAndroid::folly_runtime
ReactAndroid::glog
ReactAndroid::jsi
ReactAndroid::reactnativejni
fbjni::fbjni
)
else()
find_library(
JSI_LIB
jsi
PATHS ${LIBRN_DIR}
NO_DEFAULT_PATH
NO_CMAKE_FIND_ROOT_PATH
)
find_library(
GLOG_LIB
glog
PATHS ${LIBRN_DIR}
NO_DEFAULT_PATH
NO_CMAKE_FIND_ROOT_PATH
)
find_library(
FBJNI_LIB
fbjni
PATHS ${LIBRN_DIR}
NO_DEFAULT_PATH
NO_CMAKE_FIND_ROOT_PATH
)
if(${REACT_NATIVE_MINOR_VERSION} LESS 69)
find_library(
FOLLY_LIB
folly_json
PATHS ${LIBRN_DIR}
NO_DEFAULT_PATH
NO_CMAKE_FIND_ROOT_PATH
)
else()
find_library(
FOLLY_LIB
folly_runtime
PATHS ${LIBRN_DIR}
NO_DEFAULT_PATH
NO_CMAKE_FIND_ROOT_PATH
)
endif()
find_library(
REACTNATIVEJNI_LIB
reactnativejni
PATHS ${LIBRN_DIR}
NO_DEFAULT_PATH
NO_CMAKE_FIND_ROOT_PATH
)
target_link_libraries(
${PACKAGE_NAME}
${JSI_LIB}
${GLOG_LIB}
${FBJNI_LIB}
${FOLLY_LIB}
${REACTNATIVEJNI_LIB}
)
endif()
if(${JS_RUNTIME} STREQUAL "hermes")
string(APPEND CMAKE_CXX_FLAGS " -DJS_RUNTIME_HERMES=1")
if(${REACT_NATIVE_MINOR_VERSION} GREATER_EQUAL 71)
# From prefab from module `com.facebook.react:hermes-android`
set(HERMES_LIB hermes-engine::libhermes)
elseif(${REACT_NATIVE_MINOR_VERSION} GREATER_EQUAL 69)
# Bundled Hermes from module `com.facebook.react:hermes-engine` or project `:ReactAndroid:hermes-engine`
target_include_directories(
${PACKAGE_NAME}
PRIVATE
"${JS_RUNTIME_DIR}/API"
"${JS_RUNTIME_DIR}/public"
)
set(HERMES_LIB "${BUILD_DIR}/third-party-ndk/hermes/jni/${ANDROID_ABI}/libhermes.so")
else()
# From `hermes-engine` npm package
target_include_directories(
${PACKAGE_NAME}
PRIVATE
"${JS_RUNTIME_DIR}/android/include"
)
set(HERMES_LIB "${BUILD_DIR}/third-party-ndk/hermes/jni/${ANDROID_ABI}/libhermes.so")
endif()
target_link_libraries(
${PACKAGE_NAME}
${HERMES_LIB}
)
if (${HERMES_ENABLE_DEBUGGER})
if(${REACT_NATIVE_MINOR_VERSION} GREATER_EQUAL 71)
set(HERMES_EXECUTOR_LIB ReactAndroid::hermes_executor)
else()
find_library(
HERMES_EXECUTOR_LIB
hermes-executor-debug
PATHS ${LIBRN_DIR}
NO_DEFAULT_PATH
NO_CMAKE_FIND_ROOT_PATH
)
endif()
if(${REACT_NATIVE_MINOR_VERSION} LESS_EQUAL 67)
find_library(
HERMES_INSPECTOR_LIB
hermes-inspector
PATHS ${LIBRN_DIR}
NO_DEFAULT_PATH
NO_CMAKE_FIND_ROOT_PATH
)
target_link_libraries(
${PACKAGE_NAME}
${HERMES_INSPECTOR_LIB}
)
endif()
target_link_libraries(
${PACKAGE_NAME}
${HERMES_EXECUTOR_LIB}
)
endif()
elseif(${JS_RUNTIME} STREQUAL "v8")
string(APPEND CMAKE_CXX_FLAGS " -DJS_RUNTIME_V8=1")
target_include_directories(
${PACKAGE_NAME}
PRIVATE
"${JS_RUNTIME_DIR}/src"
)
file (GLOB V8_SO_DIR "${JS_RUNTIME_DIR}/android/build/intermediates/library_jni/*/jni/${ANDROID_ABI}")
find_library(
V8EXECUTOR_LIB
v8executor
PATHS ${V8_SO_DIR}
NO_DEFAULT_PATH
NO_CMAKE_FIND_ROOT_PATH
)
target_link_libraries(
${PACKAGE_NAME}
${V8EXECUTOR_LIB}
)
elseif(${JS_RUNTIME} STREQUAL "jsc")
string(APPEND CMAKE_CXX_FLAGS " -DJS_RUNTIME_JSC=1")
if(${REACT_NATIVE_MINOR_VERSION} GREATER_EQUAL 71)
set(JSEXECUTOR_LIB ReactAndroid::jscexecutor)
else()
find_library(
JSEXECUTOR_LIB
jscexecutor
PATHS ${LIBRN_DIR}
NO_DEFAULT_PATH
NO_CMAKE_FIND_ROOT_PATH
)
endif()
target_link_libraries(${PACKAGE_NAME} ${JSEXECUTOR_LIB})
else()
message(FATAL_ERROR "Unknown JS runtime ${JS_RUNTIME}.")
endif()
if(${IS_NEW_ARCHITECTURE_ENABLED})
target_link_libraries(
${PACKAGE_NAME}
ReactAndroid::fabricjni
ReactAndroid::react_debug
ReactAndroid::react_render_core
ReactAndroid::react_render_mounting
ReactAndroid::react_render_scheduler
ReactAndroid::react_render_uimanager
ReactAndroid::rrc_view
)
endif()
# Resolves "CMake Warning: Manually-specified variables were not used by the project"
# when any of the following variables is not used in some build configuration.
set (ignoreMe "${JS_RUNTIME_DIR}")
set (ignoreMe "${BOOST_VERSION}")

View File

@@ -0,0 +1,15 @@
README
======
If you want to publish the lib as a maven dependency, follow these steps before publishing a new version to npm:
1. Be sure to have the Android [SDK](https://developer.android.com/studio/index.html) and [NDK](https://developer.android.com/ndk/guides/index.html) installed
2. Be sure to have a `local.properties` file in this folder that points to the Android SDK and NDK
```
ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle
sdk.dir=/Users/{username}/Library/Android/sdk
```
3. Delete the `maven` folder
4. Run `sudo ./gradlew installArchives`
5. Verify that latest set of generated files is in the maven folder with the correct version number

View File

@@ -0,0 +1,921 @@
import com.android.Version
import org.apache.tools.ant.filters.ReplaceTokens
import org.apache.tools.ant.taskdefs.condition.Os
import groovy.json.JsonSlurper
import javax.inject.Inject
import java.nio.file.Files
import java.nio.file.Paths
/**
* Finds the path of the installed npm package with the given name using Node's
* module resolution algorithm, which searches "node_modules" directories up to
* the file system root. This handles various cases, including:
*
* - Working in the open-source RN repo:
* Gradle: /path/to/react-native/ReactAndroid
* Node module: /path/to/react-native/node_modules/[package]
*
* - Installing RN as a dependency of an app and searching for hoisted
* dependencies:
* Gradle: /path/to/app/node_modules/react-native/ReactAndroid
* Node module: /path/to/app/node_modules/[package]
*
* - Working in a larger repo (e.g., Facebook) that contains RN:
* Gradle: /path/to/repo/path/to/react-native/ReactAndroid
* Node module: /path/to/repo/node_modules/[package]
*
* The search begins at the given base directory (a File object). The returned
* path is a string.
*/
static def findNodeModulePath(baseDir, packageName) {
def basePath = baseDir.toPath().normalize()
// Node's module resolution algorithm searches up to the root directory,
// after which the base path will be null
while (basePath) {
def candidatePath = Paths.get(basePath.toString(), "node_modules", packageName)
if (candidatePath.toFile().exists()) {
return candidatePath.toString()
}
basePath = basePath.getParent()
}
return null
}
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
def safeAppExtGet(prop, fallback) {
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
appProject?.ext?.has(prop) ? appProject.ext.get(prop) : fallback
}
def resolveBuildType() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests()['args'].toString()
return tskReqStr.contains('Release') ? 'release' : 'debug'
}
def isReanimatedExampleApp() {
return safeAppExtGet("isReanimatedExampleApp", false)
}
def isNewArchitectureEnabled() {
// To opt-in for the New Architecture, you can either:
// - Set `newArchEnabled` to true inside the `gradle.properties` file
// - Invoke gradle with `-newArchEnabled=true`
// - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}
def resolveReactNativeDirectory() {
def reactNativeLocation = safeAppExtGet("REACT_NATIVE_NODE_MODULES_DIR", null)
if (reactNativeLocation != null) {
return file(reactNativeLocation)
}
if (isReanimatedExampleApp()) {
return file("$projectDir/../${getPlaygroundAppName()}/node_modules/react-native")
}
// monorepo workaround
// react-native can be hoisted or in project's own node_modules
def reactNativeFromProjectNodeModules = file("${rootProject.projectDir}/../node_modules/react-native")
if (reactNativeFromProjectNodeModules.exists()) {
return reactNativeFromProjectNodeModules
}
def reactNativeFromNodeModulesWithReanimated = file("${projectDir}/../../react-native")
if (reactNativeFromNodeModulesWithReanimated.exists()) {
return reactNativeFromNodeModulesWithReanimated
}
throw new GradleException(
"[Reanimated] Unable to resolve react-native location in node_modules. You should project extension property (in `app/build.gradle`) `REACT_NATIVE_NODE_MODULES_DIR` with path to react-native."
)
}
def getPlaygroundAppName() { // only for the development
String playgroundAppName = ""
try {
rootProject.getSubprojects().forEach({project ->
if (project.plugins.hasPlugin("com.android.application")) {
var projectCatalogAbsolutePath = project.projectDir.toString().replace("/android/app", "")
var slashPosition = projectCatalogAbsolutePath.lastIndexOf("/")
playgroundAppName = projectCatalogAbsolutePath.substring(slashPosition + 1)
}
})
} catch (_) {
throw new GradleException("[Reanimated] Couldn't determine playground app name.")
}
return playgroundAppName
}
def getReanimatedVersion() {
def inputFile = file(projectDir.path + '/../package.json')
def json = new JsonSlurper().parseText(inputFile.text)
return json.version
}
def getReanimatedMajorVersion() {
def (major, minor, patch) = getReanimatedVersion().tokenize('.')
return major.toInteger()
}
def toPlatformFileString(String path) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
path = path.replace(File.separatorChar, '/' as char)
}
return path
}
if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
}
def reactNativeRootDir = resolveReactNativeDirectory()
def reactProperties = new Properties()
file("$reactNativeRootDir/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }
def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME")
def REACT_NATIVE_MINOR_VERSION = REACT_NATIVE_VERSION.startsWith("0.0.0-") ? 1000 : REACT_NATIVE_VERSION.split("\\.")[1].toInteger()
def REANIMATED_VERSION = getReanimatedVersion()
def REANIMATED_MAJOR_VERSION = getReanimatedMajorVersion()
def IS_NEW_ARCHITECTURE_ENABLED = isNewArchitectureEnabled()
// for React Native <= 0.70
def BOOST_VERSION = reactProperties.getProperty("BOOST_VERSION")
def DOUBLE_CONVERSION_VERSION = reactProperties.getProperty("DOUBLE_CONVERSION_VERSION")
def FOLLY_VERSION = reactProperties.getProperty("FOLLY_VERSION")
def GLOG_VERSION = reactProperties.getProperty("GLOG_VERSION")
def FBJNI_VERSION = "0.3.0"
// We download various C++ open-source dependencies into downloads.
// We then copy both the downloaded code and our custom makefiles and headers into third-party-ndk.
// After that we build native code from src/main/jni with module path pointing at third-party-ndk.
def customDownloadsDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR")
def downloadsDir = customDownloadsDir ? new File(customDownloadsDir) : new File("$buildDir/downloads")
def thirdPartyNdkDir = new File("$buildDir/third-party-ndk")
def reactNativeThirdParty = new File("$reactNativeRootDir/ReactAndroid/src/main/jni/third-party")
def reactNativeAndroidDownloadDir = new File("$reactNativeRootDir/ReactAndroid/build/downloads")
def prefabHeadersDir = project.file("$buildDir/prefab-headers/reanimated")
def JS_RUNTIME = {
// Override JS runtime with environment variable
if (System.getenv("JS_RUNTIME")) {
return System.getenv("JS_RUNTIME")
}
// Enable V8 runtime if react-native-v8 is installed
def v8Project = rootProject.getSubprojects().find { project -> project.name == "react-native-v8" }
if (v8Project != null) {
return "v8"
}
// Check if Hermes is enabled in app setup
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
if ((REACT_NATIVE_MINOR_VERSION >= 71 && appProject?.hermesEnabled?.toBoolean()) || appProject?.ext?.react?.enableHermes?.toBoolean()) {
return "hermes"
}
// Use JavaScriptCore (JSC) by default
return "jsc"
}.call()
def jsRuntimeDir = {
if (JS_RUNTIME == "hermes") {
if (REACT_NATIVE_MINOR_VERSION >= 69) {
return Paths.get(reactNativeRootDir.path, "sdks", "hermes")
} else {
return Paths.get(reactNativeRootDir.path, "..", "hermes-engine")
}
} else if (JS_RUNTIME == "v8") {
return findProject(":react-native-v8").getProjectDir().getParent()
} else {
return Paths.get(reactNativeRootDir.path, "ReactCommon", "jsi")
}
}.call()
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
buildscript {
repositories {
google()
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "com.android.tools.build:gradle:7.3.1"
classpath "de.undercouch:gradle-download-task:5.0.1"
classpath "com.diffplug.spotless:spotless-plugin-gradle:6.11.0"
}
}
if (project == rootProject) {
apply from: "spotless.gradle"
}
apply plugin: "com.android.library"
apply plugin: "maven-publish"
apply plugin: "de.undercouch.download"
android {
compileSdkVersion safeExtGet("compileSdkVersion", 30)
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
if (agpVersion.tokenize('.')[0].toInteger() >= 7) {
namespace "com.swmansion.reanimated"
}
if (rootProject.hasProperty("ndkPath")) {
ndkPath rootProject.ext.ndkPath
}
if (rootProject.hasProperty("ndkVersion")) {
ndkVersion rootProject.ext.ndkVersion
}
buildFeatures {
if (REACT_NATIVE_MINOR_VERSION > 68) {
prefab true
prefabPublishing true
buildConfig true
}
}
prefab {
reanimated {
headers prefabHeadersDir.absolutePath
}
}
defaultConfig {
minSdkVersion safeExtGet("minSdkVersion", 16)
targetSdkVersion safeExtGet("targetSdkVersion", 30)
versionCode 1
versionName "1.0"
buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", IS_NEW_ARCHITECTURE_ENABLED.toString())
buildConfigField("String", "REANIMATED_VERSION_JAVA", "\"${REANIMATED_VERSION}\"")
externalNativeBuild {
cmake {
arguments "-DANDROID_STL=c++_shared",
"-DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}",
"-DANDROID_TOOLCHAIN=clang",
REACT_NATIVE_MINOR_VERSION < 71 ? "-DBOOST_VERSION=${BOOST_VERSION}" : "-DBOOST_VERSION=",
"-DREACT_NATIVE_DIR=${toPlatformFileString(reactNativeRootDir.path)}",
"-DJS_RUNTIME=${JS_RUNTIME}",
"-DJS_RUNTIME_DIR=${jsRuntimeDir}",
"-DIS_NEW_ARCHITECTURE_ENABLED=${IS_NEW_ARCHITECTURE_ENABLED}",
"-DIS_REANIMATED_EXAMPLE_APP=${isReanimatedExampleApp()}",
"-DREANIMATED_VERSION=${REANIMATED_VERSION}"
abiFilters (*reactNativeArchitectures())
}
}
buildConfigField("boolean", "IS_INTERNAL_BUILD", "false")
buildConfigField("int", "EXOPACKAGE_FLAGS", "0")
buildConfigField("int", "REACT_NATIVE_MINOR_VERSION", REACT_NATIVE_MINOR_VERSION.toString())
consumerProguardFiles 'proguard-rules.pro'
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
buildTypes {
debug {
externalNativeBuild {
cmake {
if (JS_RUNTIME == "hermes") {
arguments "-DHERMES_ENABLE_DEBUGGER=1"
} else {
arguments "-DHERMES_ENABLE_DEBUGGER=0"
}
}
}
}
release {
externalNativeBuild {
cmake {
arguments "-DHERMES_ENABLE_DEBUGGER=0"
}
}
}
}
lintOptions {
abortOnError false
}
packagingOptions {
doNotStrip resolveBuildType() == 'debug' ? "**/**/*.so" : ''
excludes = [
"META-INF",
"META-INF/**",
"**/libc++_shared.so",
"**/libfbjni.so",
"**/libjsi.so",
"**/libfolly_json.so",
"**/libfolly_runtime.so",
"**/libglog.so",
"**/libhermes.so",
"**/libhermes-executor-debug.so",
"**/libhermes_executor.so",
"**/libreactnativejni.so",
"**/libturbomodulejsijni.so",
"**/libreact_nativemodule_core.so",
"**/libjscexecutor.so",
"**/libv8executor.so",
]
}
tasks.withType(JavaCompile) {
compileTask ->
compileTask.dependsOn(packageNdkLibs)
}
configurations {
extractHeaders
extractSO
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
packagingOptions {
// For some reason gradle only complains about the duplicated version of librrc_root and libreact_render libraries
// while there are more libraries copied in intermediates folder of the lib build directory, we exclude
// only the ones that make the build fail (ideally we should only include libreanimated but we
// are only allowed to specify exlude patterns)
exclude "**/libreact_render*.so"
exclude "**/librrc_root.so"
}
sourceSets.main {
java {
if (IS_NEW_ARCHITECTURE_ENABLED) {
srcDirs += "src/fabric/java"
} else {
srcDirs += "src/paper/java"
}
// messageQueueThread
if (REANIMATED_MAJOR_VERSION > 2) {
if (REACT_NATIVE_MINOR_VERSION <= 72) {
srcDirs += "src/reactNativeVersionPatch/messageQueueThread/72"
} else {
srcDirs += "src/reactNativeVersionPatch/messageQueueThread/latest"
}
}
// ReanimatedUIManager & ReanimatedUIImplementation
if (REACT_NATIVE_MINOR_VERSION <= 73) {
srcDirs += "src/reactNativeVersionPatch/ReanimatedUIManager/73"
} else {
srcDirs += "src/reactNativeVersionPatch/ReanimatedUIManager/latest"
}
// ReactFeatureFlags
if (IS_NEW_ARCHITECTURE_ENABLED) {
if (REACT_NATIVE_MINOR_VERSION <= 72) {
srcDirs += "src/reactNativeVersionPatch/ReactFeatureFlagsWrapper/72"
} else {
srcDirs += "src/reactNativeVersionPatch/ReactFeatureFlagsWrapper/latest"
}
}
// RuntimeExecutor
if (IS_NEW_ARCHITECTURE_ENABLED) {
if (REACT_NATIVE_MINOR_VERSION <= 73) {
srcDirs += "src/reactNativeVersionPatch/RuntimeExecutor/73"
} else {
srcDirs += "src/reactNativeVersionPatch/RuntimeExecutor/latest"
}
}
}
}
}
def assertLatestReactNativeWithNewArchitecture = task assertLatestReactNativeWithNewArchitectureTask {
onlyIf { IS_NEW_ARCHITECTURE_ENABLED && REANIMATED_MAJOR_VERSION == 3 && REACT_NATIVE_MINOR_VERSION < 72 }
doFirst {
// If you change the minimal React Native version remember to update Compatibility Table in docs
throw new GradleException(
"[Reanimated] Outdated version of React Native for New Architecture. Reanimated " + REANIMATED_VERSION + " supports the New Architecture on React Native 0.72.0+. See https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#outdated-version-of-react-native-for-new-architecture for more information."
)
}
}
def assertMinimalReactNativeVersion = task assertMinimalReactNativeVersionTask {
onlyIf { REACT_NATIVE_MINOR_VERSION < 66 }
doFirst {
// If you change the minimal React Native version remember to update Compatibility Table in docs
throw new GradleException("[Reanimated] Unsupported React Native version. Please use 0.66 or newer.")
}
}
task prepareHeadersForPrefab(type: Copy) {
from("$projectDir/src/main/cpp")
from("$projectDir/../Common/cpp/AnimatedSensor")
from("$projectDir/../Common/cpp/Fabric")
from("$projectDir/../Common/cpp/LayoutAnimations")
from("$projectDir/../Common/cpp/NativeModules")
from("$projectDir/../Common/cpp/ReanimatedRuntime")
from("$projectDir/../Common/cpp/Registries")
from("$projectDir/../Common/cpp/SharedItems")
from("$projectDir/../Common/cpp/Tools")
include("*.h")
into(prefabHeadersDir)
}
tasks.preBuild {
dependsOn assertLatestReactNativeWithNewArchitecture, assertMinimalReactNativeVersion
}
task cleanCmakeCache() {
tasks.getByName("clean").dependsOn(cleanCmakeCache)
doFirst {
delete "${projectDir}/.cxx"
}
}
task printVersions {
println "Android gradle plugin: ${Version.ANDROID_GRADLE_PLUGIN_VERSION}"
println "Gradle: ${project.gradle.gradleVersion}"
}
task createNativeDepsDirectories() {
downloadsDir.mkdirs()
thirdPartyNdkDir.mkdirs()
prefabHeadersDir.mkdirs()
}
def resolveTaskFactory(String taskName, String artifactLocalName, File reactNativeAndroidDownloadDir, File reanimatedDownloadDir) {
return tasks.create(name: taskName, dependsOn: createNativeDepsDirectories, type: Copy) {
from reactNativeAndroidDownloadDir
include artifactLocalName
into reanimatedDownloadDir
onlyIf {
// First we check whether the file is already in our download directory
if (file("$reanimatedDownloadDir/$artifactLocalName").isFile()) {
return false
}
// If it is not the case we check whether it was downloaded by ReactAndroid project
if (file("$reactNativeAndroidDownloadDir/$artifactLocalName").isFile()) {
return true
}
return false
}
}
}
/*
Reanimated includes "hermes/hermes.h" header file in `NativeProxy.cpp`.
Previously, we used header files from `hermes-engine` package in `node_modules`.
In React Native 0.69 and 0.70, Hermes is no longer distributed as package on NPM.
On the new architecture, Hermes is downloaded from GitHub and then compiled from sources.
However, on the old architecture, we need to download Hermes header files on our own
as well as unzip Hermes AAR in order to obtain `libhermes.so` shared library.
For more details, see https://reactnative.dev/architecture/bundled-hermes
or https://github.com/reactwg/react-native-new-architecture/discussions/4
*/
if (REACT_NATIVE_MINOR_VERSION in [69, 70] && !IS_NEW_ARCHITECTURE_ENABLED) {
// copied from `react-native/ReactAndroid/hermes-engine/build.gradle`
def downloadDir = customDownloadsDir ? new File(customDownloadsDir) : new File(reactNativeRootDir, "sdks/download")
// By default we are going to download and unzip hermes inside the /sdks/hermes folder
// but you can provide an override for where the hermes source code is located.
def hermesDir = System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR") ?: new File(reactNativeRootDir, "sdks/hermes")
def hermesVersion = "main"
def hermesVersionFile = new File(reactNativeRootDir, "sdks/.hermesversion")
if (hermesVersionFile.exists()) {
hermesVersion = hermesVersionFile.text
}
task downloadHermes(type: Download) {
src("https://github.com/facebook/hermes/tarball/${hermesVersion}")
onlyIfNewer(true)
overwrite(false)
dest(new File(downloadDir, "hermes.tar.gz"))
}
task unzipHermes(dependsOn: downloadHermes, type: Copy) {
from(tarTree(downloadHermes.dest)) {
eachFile { file ->
// We flatten the unzip as the tarball contains a `facebook-hermes-<SHA>`
// folder at the top level.
if (file.relativePath.segments.size() > 1) {
file.relativePath = new RelativePath(!file.isDirectory(), file.relativePath.segments.drop(1))
}
}
}
into(hermesDir)
}
}
if (REACT_NATIVE_MINOR_VERSION < 71) {
// You need to have following folders in this directory:
// - boost_1_63_0
// - double-conversion-1.1.6
// - folly-deprecate-dynamic-initializer
// - glog-0.3.5
def dependenciesPath = System.getenv("REACT_NATIVE_DEPENDENCIES")
// The Boost library is a very large download (>100MB).
// If Boost is already present on your system, define the REACT_NATIVE_BOOST_PATH env variable
// and the build will use that.
def boostPath = dependenciesPath ?: System.getenv("REACT_NATIVE_BOOST_PATH")
def follyReplaceContent = '''
ssize_t r;
do {
r = open(name, flags, mode);
} while (r == -1 && errno == EINTR);
return r;
'''
Task resolveBoost = resolveTaskFactory("resolveBoost", "boost_${BOOST_VERSION}.tar.gz", reactNativeAndroidDownloadDir, downloadsDir)
Task resolveDoubleConversion = resolveTaskFactory(
"resolveDoubleConversion",
"double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz",
reactNativeAndroidDownloadDir,
downloadsDir
)
Task resolveFolly = resolveTaskFactory("resolveFolly", "folly-${FOLLY_VERSION}.tar.gz", reactNativeAndroidDownloadDir, downloadsDir)
Task resolveGlog = resolveTaskFactory("resolveGlog", "glog-${GLOG_VERSION}.tar.gz", reactNativeAndroidDownloadDir, downloadsDir)
if (IS_NEW_ARCHITECTURE_ENABLED) {
def reactNativeAndroidProject = findProject(":ReactAndroid")
if (reactNativeAndroidProject != null) {
reactNativeAndroidProject.afterEvaluate {
def resolveTasks = [resolveBoost, resolveGlog, resolveDoubleConversion, resolveFolly]
resolveTasks.forEach({ task ->
String reactAndroidDownloadTaskName = "download" + task.name.replace("resolve", "")
def reactAndroidDownloadTask = reactNativeAndroidProject.getTasks().findByName(reactAndroidDownloadTaskName)
if (reactAndroidDownloadTask != null) {
task.dependsOn(reactAndroidDownloadTask)
} else {
logger.warn("[Reanimated] Failed to find task named `$reactAndroidDownloadTaskName` in `:ReactAndroid` project." +
" Explicit dependency between it and $task.name task can not be set.")
}
})
}
} else {
throw new GradleException("[Reanimated] Failed to find `:ReactAndroid` project. Explicit dependency between download tasks can not be set.")
}
}
task downloadBoost(dependsOn: resolveBoost, type: Download) {
def transformedVersion = BOOST_VERSION.replace("_", ".")
def artifactLocalName = "boost_${BOOST_VERSION}.tar.gz"
def srcUrl = "https://archives.boost.io/release/${transformedVersion}/source/${artifactLocalName}"
if (REACT_NATIVE_MINOR_VERSION < 69) {
srcUrl = "https://github.com/react-native-community/boost-for-react-native/releases/download/v${transformedVersion}-0/${artifactLocalName}"
}
src(srcUrl)
onlyIfNewer(true)
overwrite(false)
dest(new File(downloadsDir, artifactLocalName))
}
task prepareBoost(dependsOn: boostPath ? [] : [downloadBoost], type: Copy) {
from(boostPath ?: tarTree(resources.gzip(downloadBoost.dest)))
from("$reactNativeThirdParty/boost/Android.mk")
include("Android.mk", "boost_${BOOST_VERSION}/boost/**/*.hpp", "boost/boost/**/*.hpp")
includeEmptyDirs = false
into("$thirdPartyNdkDir/boost")
doLast {
file("$thirdPartyNdkDir/boost/boost").renameTo("$thirdPartyNdkDir/boost/boost_${BOOST_VERSION}")
}
}
task downloadDoubleConversion(dependsOn: resolveDoubleConversion, type: Download) {
src("https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz")
onlyIfNewer(true)
overwrite(false)
dest(new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz"))
}
task prepareDoubleConversion(dependsOn: dependenciesPath ? [] : [downloadDoubleConversion], type: Copy) {
from(dependenciesPath ?: tarTree(downloadDoubleConversion.dest))
from("$reactNativeThirdParty/double-conversion/Android.mk")
include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "Android.mk")
filesMatching("*/src/**/*", { fname -> fname.path = "double-conversion/${fname.name}" })
includeEmptyDirs = false
into("$thirdPartyNdkDir/double-conversion")
}
task downloadFolly(dependsOn: resolveFolly, type: Download) {
src("https://github.com/facebook/folly/archive/v${FOLLY_VERSION}.tar.gz")
onlyIfNewer(true)
overwrite(false)
dest(new File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz"))
}
task prepareFolly(dependsOn: dependenciesPath ? [] : [downloadFolly], type: Copy) {
from(dependenciesPath ?: tarTree(downloadFolly.dest))
from("$reactNativeThirdParty/folly/Android.mk")
include("folly-${FOLLY_VERSION}/folly/**/*", "Android.mk")
eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") }
// Fixes problem with Folly failing to build on certain systems. See
// https://github.com/software-mansion/react-native-reanimated/issues/1024
filter { line -> line.replaceAll("return int\\(wrapNoInt\\(open, name, flags, mode\\)\\);", follyReplaceContent) }
includeEmptyDirs = false
into("$thirdPartyNdkDir/folly")
}
task downloadGlog(dependsOn: resolveGlog, type: Download) {
src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz")
onlyIfNewer(true)
overwrite(false)
dest(new File(downloadsDir, "glog-${GLOG_VERSION}.tar.gz"))
}
// Prepare glog sources to be compiled, this task will perform steps that normally should've been
// executed by automake. This way we can avoid dependencies on make/automake
task prepareGlog(dependsOn: dependenciesPath ? [] : [downloadGlog], type: Copy) {
duplicatesStrategy = "include"
from(dependenciesPath ?: tarTree(downloadGlog.dest))
from("$reactNativeThirdParty/glog/")
include("glog-${GLOG_VERSION}/src/**/*", "Android.mk", "config.h")
includeEmptyDirs = false
filesMatching("**/*.h.in") {
filter(ReplaceTokens, tokens: [
ac_cv_have_unistd_h : "1",
ac_cv_have_stdint_h : "1",
ac_cv_have_systypes_h : "1",
ac_cv_have_inttypes_h : "1",
ac_cv_have_libgflags : "0",
ac_google_start_namespace : "namespace google {",
ac_cv_have_uint16_t : "1",
ac_cv_have_u_int16_t : "1",
ac_cv_have___uint16 : "0",
ac_google_end_namespace : "}",
ac_cv_have___builtin_expect : "1",
ac_google_namespace : "google",
ac_cv___attribute___noinline : "__attribute__ ((noinline))",
ac_cv___attribute___noreturn : "__attribute__ ((noreturn))",
ac_cv___attribute___printf_4_5: "__attribute__((__format__ (__printf__, 4, 5)))"
])
it.path = (it.name - ".in")
}
into("$thirdPartyNdkDir/glog")
doLast {
copy {
from(fileTree(dir: "$thirdPartyNdkDir/glog", includes: ["stl_logging.h", "logging.h", "raw_logging.h", "vlog_is_on.h", "**/src/glog/log_severity.h"]).files)
includeEmptyDirs = false
into("$thirdPartyNdkDir/glog/exported/glog")
}
}
}
task prepareHermes {
if (REACT_NATIVE_MINOR_VERSION >= 69) {
if (!IS_NEW_ARCHITECTURE_ENABLED) {
dependsOn(unzipHermes)
}
doLast {
// e.g. hermes-engine-0.70.0-rc.1-debug.aar
def hermesAAR = file(
"$reactNativeRootDir/android/com/facebook/react/hermes-engine/" +
"${REACT_NATIVE_VERSION}/hermes-engine-${REACT_NATIVE_VERSION}-" +
"${resolveBuildType()}.aar"
)
if (!hermesAAR.exists()) {
throw new GradleException("[Reanimated] Could not find hermes-engine AAR.", null)
}
def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" })
copy {
from soFiles
from "$reactNativeRootDir/ReactAndroid/src/main/jni/first-party/hermes/Android.mk"
into "$thirdPartyNdkDir/hermes"
}
}
} else {
doLast {
def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine")
if (!hermesPackagePath) {
throw new GradleException("[Reanimated] Could not find the hermes-engine npm package.", null)
}
def hermesAAR = file("$hermesPackagePath/android/hermes-${resolveBuildType()}.aar") // e.g. hermes-debug.aar
if (!hermesAAR.exists()) {
throw new GradleException("[Reanimated] The hermes-engine npm package is missing \"android/hermes-${resolveBuildType()}.aar\".", null)
}
def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" })
copy {
from soFiles
from "$reactNativeRootDir/ReactAndroid/src/main/jni/first-party/hermes/Android.mk"
into "$thirdPartyNdkDir/hermes"
}
}
}
}
task prepareJSC {
if (REACT_NATIVE_MINOR_VERSION >= 71) {
// do nothing
} else {
doLast {
def jscPackagePath = findNodeModulePath(projectDir, "jsc-android")
if (!jscPackagePath) {
throw new GradleException("[Reanimated] Could not find the jsc-android npm package.", null)
}
def jscDist = file("$jscPackagePath/dist")
if (!jscDist.exists()) {
throw new GradleException("[Reanimated] The jsc-android npm package is missing its \"dist\" directory.", null)
}
def jscAAR = fileTree(jscDist).matching({ it.include "**/android-jsc/**/*.aar" }).singleFile
def soFiles = zipTree(jscAAR).matching({ it.include "**/*.so" })
def headerFiles = fileTree(jscDist).matching({ it.include "**/include/*.h" })
copy {
from(soFiles)
from(headerFiles)
from("$reactNativeRootDir/ReactAndroid/src/main/jni/third-party/jsc/Android.mk")
filesMatching("**/*.h", { it.path = "JavaScriptCore/${it.name}" })
includeEmptyDirs(false)
into("$thirdPartyNdkDir/jsc")
}
}
}
}
task extractAARHeaders {
doLast {
configurations.extractHeaders.files.each {
def file = it.absoluteFile
def packageName = file.name.tokenize('-')[0]
copy {
from zipTree(file)
into "$reactNativeRootDir/ReactAndroid/src/main/jni/first-party/$packageName/headers"
include "**/*.h"
}
}
}
}
task extractSOFiles {
doLast {
configurations.extractSO.files.each {
def file = it.absoluteFile
def packageName = file.name.tokenize('-')[0]
copy {
from zipTree(file)
into "$reactNativeRootDir/ReactAndroid/src/main/jni/first-party/$packageName/"
include "jni/**/*.so"
}
}
}
}
task unpackReactNativeAAR {
def buildType = resolveBuildType()
def rnAarMatcher = "**/react-native/**/*${buildType}.aar"
if (REACT_NATIVE_MINOR_VERSION < 69) {
rnAarMatcher = "**/**/*.aar"
}
def rnAAR = fileTree("$reactNativeRootDir/android").matching({ it.include rnAarMatcher }).singleFile
def file = rnAAR.absoluteFile
def packageName = file.name.tokenize('-')[0]
copy {
from zipTree(file)
into "$reactNativeRootDir/ReactAndroid/src/main/jni/first-party/$packageName/"
include "jni/**/*.so"
}
}
task downloadNdkBuildDependencies {
if (!boostPath) {
dependsOn(downloadBoost)
}
dependsOn(downloadDoubleConversion)
dependsOn(downloadFolly)
dependsOn(downloadGlog)
}
task prepareThirdPartyNdkHeaders(dependsOn:[
downloadNdkBuildDependencies,
prepareBoost,
prepareDoubleConversion,
prepareFolly,
prepareGlog,
unpackReactNativeAAR]
) {}
}
task packageNdkLibs(type: Copy) {
from("$buildDir/reanimated-ndk/all")
include("**/libreanimated.so")
into("$projectDir/src/main/jniLibs")
}
repositories {
mavenCentral()
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$reactNativeRootDir/android"
}
maven {
// Android JSC is installed from npm
url "$reactNativeRootDir/../jsc-android/dist"
}
google()
}
dependencies {
implementation "com.facebook.yoga:proguard-annotations:1.19.0"
implementation "androidx.transition:transition:1.1.0"
implementation "androidx.core:core:1.6.0"
if (REACT_NATIVE_MINOR_VERSION >= 71) {
implementation "com.facebook.react:react-android" // version substituted by RNGP
if (JS_RUNTIME == "hermes") {
implementation "com.facebook.react:hermes-android" // version substituted by RNGP
}
} else {
// noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "com.facebook.fbjni:fbjni-java-only:" + FBJNI_VERSION
extractHeaders("com.facebook.fbjni:fbjni:" + FBJNI_VERSION + ":headers")
extractSO("com.facebook.fbjni:fbjni:" + FBJNI_VERSION)
def jscAAR = fileTree("$reactNativeRootDir/../jsc-android/dist/org/webkit/android-jsc").matching({ it.include "**/**/*.aar" }).singleFile
extractSO(files(jscAAR))
}
}
def nativeBuildDependsOn(dependsOnTask) {
def buildTasks = tasks.findAll({ task -> (
!task.name.contains("Clean")
&& (task.name.contains("externalNative")
|| task.name.contains("CMake")
|| task.name.contains("generateJsonModel")
)
) })
buildTasks.forEach { task -> task.dependsOn(dependsOnTask) }
}
afterEvaluate {
if (REACT_NATIVE_MINOR_VERSION < 71) {
extractAARHeaders.dependsOn(prepareThirdPartyNdkHeaders)
extractSOFiles.dependsOn(prepareThirdPartyNdkHeaders)
nativeBuildDependsOn(prepareThirdPartyNdkHeaders)
nativeBuildDependsOn(extractAARHeaders)
nativeBuildDependsOn(extractSOFiles)
}
preBuild.dependsOn(prepareHeadersForPrefab)
tasks.forEach({ task ->
if (task.name.contains("JniLibFolders")) {
task.dependsOn(packageNdkLibs)
}
})
if (JS_RUNTIME == "hermes") {
if (REACT_NATIVE_MINOR_VERSION < 71) {
extractAARHeaders.dependsOn(prepareHermes)
extractSOFiles.dependsOn(prepareHermes)
}
} else if (JS_RUNTIME == "v8") {
def buildTasks = tasks.findAll({ task ->
!task.name.contains("Clean") && (task.name.contains("externalNative") || task.name.contains("CMake") || task.name.startsWith("generateJsonModel")) })
buildTasks.forEach { task ->
def buildType = task.name.endsWith('Debug') ? 'Debug' : 'Release'
task.dependsOn(":react-native-v8:copy${buildType}JniLibsProjectOnly")
}
} else if (JS_RUNTIME == "jsc") {
if (REACT_NATIVE_MINOR_VERSION < 71) {
extractAARHeaders.dependsOn(prepareJSC)
extractSOFiles.dependsOn(prepareJSC)
}
} else {
throw GradleScriptException("[Reanimated] Unknown JS runtime ${JS_RUNTIME}.")
}
}

View File

@@ -0,0 +1 @@
// required for compilation purpose

View File

@@ -0,0 +1,25 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8A --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,3 @@
-keep class com.swmansion.reanimated.** { *; }
-keep class com.facebook.react.turbomodule.** { *; }
-keep class com.facebook.react.fabric.** { *; }

View File

@@ -0,0 +1 @@
include 'lib'

View File

@@ -0,0 +1,9 @@
// formatter & linter configuration for java
apply plugin: 'com.diffplug.spotless'
spotless {
java {
target 'src/main/java/**/*.java'
googleJavaFormat()
}
}

View File

@@ -0,0 +1,25 @@
package com.swmansion.reanimated;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.common.UIManagerType;
import com.facebook.react.fabric.FabricUIManager;
class ReaCompatibility {
private FabricUIManager fabricUIManager;
public ReaCompatibility(ReactApplicationContext reactApplicationContext) {
fabricUIManager = (FabricUIManager) UIManagerHelper.getUIManager(reactApplicationContext, UIManagerType.FABRIC);
}
public void registerFabricEventListener(NodesManager nodesManager) {
if (fabricUIManager != null) {
fabricUIManager.getEventDispatcher().addListener(nodesManager);
}
}
public void synchronouslyUpdateUIProps(int viewTag, ReadableMap uiProps) {
fabricUIManager.synchronouslyUpdateViewOnUIThread(viewTag, uiProps);
}
}

View File

@@ -0,0 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.swmansion.reanimated">
</manifest>

View File

@@ -0,0 +1,34 @@
#include <android/log.h>
#include <memory>
#include "AndroidLogger.h"
#include "Logger.h"
#define APPNAME "NATIVE_REANIMATED"
namespace reanimated {
std::unique_ptr<LoggerInterface> Logger::instance =
std::make_unique<AndroidLogger>();
void AndroidLogger::log(const char *str) {
__android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "%s", str);
}
void AndroidLogger::log(const std::string &str) {
log(str.c_str());
}
void AndroidLogger::log(double d) {
__android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "%f", d);
}
void AndroidLogger::log(int i) {
__android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "%d", i);
}
void AndroidLogger::log(bool b) {
log(b ? "true" : "false");
}
} // namespace reanimated

View File

@@ -0,0 +1,18 @@
#pragma once
#include <string>
#include "LoggerInterface.h"
namespace reanimated {
class AndroidLogger : public LoggerInterface {
public:
void log(const char *str) override;
void log(const std::string &str) override;
void log(double d) override;
void log(int i) override;
void log(bool b) override;
};
} // namespace reanimated

View File

@@ -0,0 +1,63 @@
#include "AndroidUIScheduler.h"
#include <android/log.h>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <memory>
#include <string>
namespace reanimated {
using namespace facebook;
using namespace react;
class UISchedulerWrapper : public UIScheduler {
private:
jni::global_ref<AndroidUIScheduler::javaobject> androidUiScheduler_;
public:
explicit UISchedulerWrapper(
jni::global_ref<AndroidUIScheduler::javaobject> androidUiScheduler)
: androidUiScheduler_(androidUiScheduler) {}
void scheduleOnUI(std::function<void()> job) override {
UIScheduler::scheduleOnUI(job);
if (!scheduledOnUI_) {
scheduledOnUI_ = true;
androidUiScheduler_->cthis()->scheduleTriggerOnUI();
}
}
~UISchedulerWrapper() {}
};
AndroidUIScheduler::AndroidUIScheduler(
jni::alias_ref<AndroidUIScheduler::javaobject> jThis)
: javaPart_(jni::make_global(jThis)),
uiScheduler_(
std::make_shared<UISchedulerWrapper>(jni::make_global(jThis))) {}
jni::local_ref<AndroidUIScheduler::jhybriddata> AndroidUIScheduler::initHybrid(
jni::alias_ref<jhybridobject> jThis) {
return makeCxxInstance(jThis);
}
void AndroidUIScheduler::triggerUI() {
uiScheduler_->triggerUI();
}
void AndroidUIScheduler::scheduleTriggerOnUI() {
static const auto method =
javaPart_->getClass()->getMethod<void()>("scheduleTriggerOnUI");
method(javaPart_.get());
}
void AndroidUIScheduler::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", AndroidUIScheduler::initHybrid),
makeNativeMethod("triggerUI", AndroidUIScheduler::triggerUI),
});
}
} // namespace reanimated

View File

@@ -0,0 +1,43 @@
#pragma once
#include <fbjni/fbjni.h>
#include <jni.h>
#include <jsi/jsi.h>
#include <react/jni/CxxModuleWrapper.h>
#include <react/jni/JMessageQueueThread.h>
#include <memory>
#include "UIScheduler.h"
namespace reanimated {
using namespace facebook;
class AndroidUIScheduler : public jni::HybridClass<AndroidUIScheduler> {
public:
static auto constexpr kJavaDescriptor =
"Lcom/swmansion/reanimated/AndroidUIScheduler;";
static jni::local_ref<jhybriddata> initHybrid(
jni::alias_ref<jhybridobject> jThis);
static void registerNatives();
std::shared_ptr<UIScheduler> getUIScheduler() {
return uiScheduler_;
}
void scheduleTriggerOnUI();
private:
friend HybridBase;
void triggerUI();
jni::global_ref<AndroidUIScheduler::javaobject> javaPart_;
std::shared_ptr<UIScheduler> uiScheduler_;
explicit AndroidUIScheduler(
jni::alias_ref<AndroidUIScheduler::jhybridobject> jThis);
};
} // namespace reanimated

View File

@@ -0,0 +1,59 @@
#include "JNIHelper.h"
namespace reanimated {
using namespace facebook::jni;
using namespace facebook;
using namespace react;
local_ref<JNIHelper::PropsMap> JNIHelper::PropsMap::create() {
return newInstance();
}
void JNIHelper::PropsMap::put(
const std::string &key,
jni::local_ref<JObject> object) {
static auto method =
getClass()
->getMethod<jobject(
jni::local_ref<JObject>, jni::local_ref<JObject>)>("put");
method(self(), jni::make_jstring(key), object);
}
jni::local_ref<JNIHelper::PropsMap> JNIHelper::ConvertToPropsMap(
jsi::Runtime &rt,
const jsi::Object &props) {
auto map = PropsMap::create();
auto propNames = props.getPropertyNames(rt);
for (size_t i = 0, size = propNames.size(rt); i < size; i++) {
auto jsiKey = propNames.getValueAtIndex(rt, i).asString(rt);
auto value = props.getProperty(rt, jsiKey);
auto key = jsiKey.utf8(rt);
if (value.isUndefined() || value.isNull()) {
map->put(key, nullptr);
} else if (value.isBool()) {
map->put(key, JBoolean::valueOf(value.getBool()));
} else if (value.isNumber()) {
map->put(key, jni::autobox(value.asNumber()));
} else if (value.isString()) {
map->put(key, jni::make_jstring(value.asString(rt).utf8(rt)));
} else if (value.isObject()) {
if (value.asObject(rt).isArray(rt)) {
map->put(
key,
ReadableNativeArray::newObjectCxxArgs(
jsi::dynamicFromValue(rt, value)));
} else {
map->put(
key,
ReadableNativeMap::newObjectCxxArgs(
jsi::dynamicFromValue(rt, value)));
}
}
}
return map;
}
}; // namespace reanimated

View File

@@ -0,0 +1,30 @@
#pragma once
#include <fbjni/fbjni.h>
#include <jsi/JSIDynamic.h>
#include <jsi/jsi.h>
#include <react/jni/CxxModuleWrapper.h>
#include <react/jni/JMessageQueueThread.h>
#include <react/jni/WritableNativeMap.h>
#include <string>
namespace reanimated {
using namespace facebook::jni;
using namespace facebook;
using namespace react;
struct JNIHelper {
struct PropsMap : jni::JavaClass<PropsMap, JMap<JString, JObject>> {
static constexpr auto kJavaDescriptor = "Ljava/util/HashMap;";
static local_ref<PropsMap> create();
void put(const std::string &key, jni::local_ref<JObject> object);
};
static jni::local_ref<PropsMap> ConvertToPropsMap(
jsi::Runtime &rt,
const jsi::Object &props);
};
}; // namespace reanimated

View File

@@ -0,0 +1,133 @@
#include "LayoutAnimations.h"
#include "FeaturesConfig.h"
#include "Logger.h"
namespace reanimated {
LayoutAnimations::LayoutAnimations(
jni::alias_ref<LayoutAnimations::javaobject> jThis)
: javaPart_(jni::make_global(jThis)) {}
jni::local_ref<LayoutAnimations::jhybriddata> LayoutAnimations::initHybrid(
jni::alias_ref<jhybridobject> jThis) {
return makeCxxInstance(jThis);
}
void LayoutAnimations::setAnimationStartingBlock(
AnimationStartingBlock animationStartingBlock) {
this->animationStartingBlock_ = animationStartingBlock;
}
void LayoutAnimations::startAnimationForTag(
int tag,
int type,
alias_ref<JMap<jstring, jstring>> values) {
this->animationStartingBlock_(tag, type, values);
}
void LayoutAnimations::progressLayoutAnimation(
int tag,
const jni::local_ref<JNIHelper::PropsMap> &updates,
bool isSharedTransition) {
static const auto method =
javaPart_->getClass()
->getMethod<void(int, JMap<JString, JObject>::javaobject, bool)>(
"progressLayoutAnimation");
method(javaPart_.get(), tag, updates.get(), isSharedTransition);
}
void LayoutAnimations::endLayoutAnimation(int tag, bool removeView) {
static const auto method =
javaPart_->getClass()->getMethod<void(int, bool)>("endLayoutAnimation");
method(javaPart_.get(), tag, removeView);
}
void LayoutAnimations::setHasAnimationBlock(
HasAnimationBlock hasAnimationBlock) {
this->hasAnimationBlock_ = hasAnimationBlock;
}
void LayoutAnimations::setShouldAnimateExitingBlock(
ShouldAnimateExitingBlock shouldAnimateExitingBlock) {
this->shouldAnimateExitingBlock_ = shouldAnimateExitingBlock;
}
#ifndef NDEBUG
void LayoutAnimations::setCheckDuplicateSharedTag(
CheckDuplicateSharedTag checkDuplicateSharedTag) {
checkDuplicateSharedTag_ = checkDuplicateSharedTag;
}
void LayoutAnimations::checkDuplicateSharedTag(int viewTag, int screenTag) {
checkDuplicateSharedTag_(viewTag, screenTag);
}
#endif
bool LayoutAnimations::hasAnimationForTag(int tag, int type) {
return hasAnimationBlock_(tag, type);
}
bool LayoutAnimations::shouldAnimateExiting(int tag, bool shouldAnimate) {
return shouldAnimateExitingBlock_(tag, shouldAnimate);
}
void LayoutAnimations::setClearAnimationConfigBlock(
ClearAnimationConfigBlock clearAnimationConfigBlock) {
this->clearAnimationConfigBlock_ = clearAnimationConfigBlock;
}
void LayoutAnimations::clearAnimationConfigForTag(int tag) {
clearAnimationConfigBlock_(tag);
}
void LayoutAnimations::setCancelAnimationForTag(
CancelAnimationBlock cancelAnimationBlock) {
this->cancelAnimationBlock_ = cancelAnimationBlock;
}
void LayoutAnimations::cancelAnimationForTag(int tag) {
this->cancelAnimationBlock_(tag);
}
bool LayoutAnimations::isLayoutAnimationEnabled() {
return FeaturesConfig::isLayoutAnimationEnabled();
}
void LayoutAnimations::setFindPrecedingViewTagForTransition(
FindPrecedingViewTagForTransitionBlock
findPrecedingViewTagForTransitionBlock) {
findPrecedingViewTagForTransitionBlock_ =
findPrecedingViewTagForTransitionBlock;
}
int LayoutAnimations::findPrecedingViewTagForTransition(int tag) {
return findPrecedingViewTagForTransitionBlock_(tag);
}
void LayoutAnimations::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", LayoutAnimations::initHybrid),
makeNativeMethod(
"startAnimationForTag", LayoutAnimations::startAnimationForTag),
makeNativeMethod(
"hasAnimationForTag", LayoutAnimations::hasAnimationForTag),
makeNativeMethod(
"shouldAnimateExiting", LayoutAnimations::shouldAnimateExiting),
makeNativeMethod(
"clearAnimationConfigForTag",
LayoutAnimations::clearAnimationConfigForTag),
makeNativeMethod(
"cancelAnimationForTag", LayoutAnimations::cancelAnimationForTag),
makeNativeMethod(
"isLayoutAnimationEnabled",
LayoutAnimations::isLayoutAnimationEnabled),
makeNativeMethod(
"findPrecedingViewTagForTransition",
LayoutAnimations::findPrecedingViewTagForTransition),
#ifndef NDEBUG
makeNativeMethod(
"checkDuplicateSharedTag", LayoutAnimations::checkDuplicateSharedTag),
#endif
});
}
}; // namespace reanimated

View File

@@ -0,0 +1,84 @@
#pragma once
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <memory>
#include <string>
#include "JNIHelper.h"
namespace reanimated {
using namespace facebook::jni;
using namespace facebook;
class LayoutAnimations : public jni::HybridClass<LayoutAnimations> {
using AnimationStartingBlock =
std::function<void(int, int, alias_ref<JMap<jstring, jstring>>)>;
using HasAnimationBlock = std::function<bool(int, int)>;
using ShouldAnimateExitingBlock = std::function<bool(int, bool)>;
#ifndef NDEBUG
using CheckDuplicateSharedTag = std::function<void(int, int)>;
#endif
using ClearAnimationConfigBlock = std::function<void(int)>;
using CancelAnimationBlock = std::function<void(int)>;
using FindPrecedingViewTagForTransitionBlock = std::function<int(int)>;
public:
static auto constexpr kJavaDescriptor =
"Lcom/swmansion/reanimated/layoutReanimation/LayoutAnimations;";
static jni::local_ref<jhybriddata> initHybrid(
jni::alias_ref<jhybridobject> jThis);
static void registerNatives();
void startAnimationForTag(
int tag,
int type,
alias_ref<JMap<jstring, jstring>> values);
bool hasAnimationForTag(int tag, int type);
bool shouldAnimateExiting(int tag, bool shouldAnimate);
bool isLayoutAnimationEnabled();
void setAnimationStartingBlock(AnimationStartingBlock animationStartingBlock);
void setHasAnimationBlock(HasAnimationBlock hasAnimationBlock);
void setShouldAnimateExitingBlock(
ShouldAnimateExitingBlock shouldAnimateExitingBlock);
#ifndef NDEBUG
void setCheckDuplicateSharedTag(
CheckDuplicateSharedTag checkDuplicateSharedTag);
void checkDuplicateSharedTag(int viewTag, int screenTag);
#endif
void setClearAnimationConfigBlock(
ClearAnimationConfigBlock clearAnimationConfigBlock);
void setCancelAnimationForTag(CancelAnimationBlock cancelAnimationBlock);
void setFindPrecedingViewTagForTransition(
FindPrecedingViewTagForTransitionBlock
findPrecedingViewTagForTransitionBlock);
void progressLayoutAnimation(
int tag,
const jni::local_ref<JNIHelper::PropsMap> &updates,
bool isSharedTransition);
void endLayoutAnimation(int tag, bool removeView);
void clearAnimationConfigForTag(int tag);
void cancelAnimationForTag(int tag);
int findPrecedingViewTagForTransition(int tag);
private:
friend HybridBase;
jni::global_ref<LayoutAnimations::javaobject> javaPart_;
AnimationStartingBlock animationStartingBlock_;
HasAnimationBlock hasAnimationBlock_;
ShouldAnimateExitingBlock shouldAnimateExitingBlock_;
ClearAnimationConfigBlock clearAnimationConfigBlock_;
CancelAnimationBlock cancelAnimationBlock_;
FindPrecedingViewTagForTransitionBlock
findPrecedingViewTagForTransitionBlock_;
#ifndef NDEBUG
CheckDuplicateSharedTag checkDuplicateSharedTag_;
#endif
explicit LayoutAnimations(
jni::alias_ref<LayoutAnimations::jhybridobject> jThis);
};
}; // namespace reanimated

View File

@@ -0,0 +1,657 @@
#include <android/log.h>
#include <fbjni/fbjni.h>
#include <jsi/JSIDynamic.h>
#include <jsi/jsi.h>
#include <react/jni/JMessageQueueThread.h>
#include <react/jni/ReadableNativeArray.h>
#include <react/jni/ReadableNativeMap.h>
#ifdef RCT_NEW_ARCH_ENABLED
#include <react/fabric/Binding.h>
#endif
#include <memory>
#include <string>
#include "AndroidUIScheduler.h"
#include "LayoutAnimationsManager.h"
#include "NativeProxy.h"
#include "PlatformDepMethodsHolder.h"
#include "RNRuntimeDecorator.h"
#include "ReanimatedJSIUtils.h"
#include "ReanimatedRuntime.h"
#include "ReanimatedVersion.h"
#include "WorkletRuntime.h"
#include "WorkletRuntimeCollector.h"
namespace reanimated {
using namespace facebook;
using namespace react;
NativeProxy::NativeProxy(
jni::alias_ref<NativeProxy::javaobject> jThis,
jsi::Runtime *rnRuntime,
const std::shared_ptr<facebook::react::CallInvoker> &jsCallInvoker,
const std::shared_ptr<UIScheduler> &uiScheduler,
jni::global_ref<LayoutAnimations::javaobject> layoutAnimations,
jni::alias_ref<JavaMessageQueueThread::javaobject> messageQueueThread,
#ifdef RCT_NEW_ARCH_ENABLED
jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
fabricUIManager,
#endif
const std::string &valueUnpackerCode)
: javaPart_(jni::make_global(jThis)),
rnRuntime_(rnRuntime),
nativeReanimatedModule_(std::make_shared<NativeReanimatedModule>(
*rnRuntime,
std::make_shared<JSScheduler>(*rnRuntime, jsCallInvoker),
std::make_shared<JMessageQueueThread>(messageQueueThread),
uiScheduler,
getPlatformDependentMethods(),
valueUnpackerCode,
/* isBridgeless */ false)),
layoutAnimations_(std::move(layoutAnimations)) {
#ifdef RCT_NEW_ARCH_ENABLED
commonInit(fabricUIManager);
#endif // RCT_NEW_ARCH_ENABLED
}
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
NativeProxy::NativeProxy(
jni::alias_ref<NativeProxy::javaobject> jThis,
jsi::Runtime *rnRuntime,
RuntimeExecutor runtimeExecutor,
const std::shared_ptr<UIScheduler> &uiScheduler,
jni::global_ref<LayoutAnimations::javaobject> layoutAnimations,
jni::alias_ref<JavaMessageQueueThread::javaobject> messageQueueThread,
jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
fabricUIManager,
const std::string &valueUnpackerCode)
: javaPart_(jni::make_global(jThis)),
rnRuntime_(rnRuntime),
nativeReanimatedModule_(std::make_shared<NativeReanimatedModule>(
*rnRuntime,
std::make_shared<JSScheduler>(*rnRuntime, runtimeExecutor),
std::make_shared<JMessageQueueThread>(messageQueueThread),
uiScheduler,
getPlatformDependentMethods(),
valueUnpackerCode,
/* isBridgeless */ true)),
layoutAnimations_(std::move(layoutAnimations)) {
commonInit(fabricUIManager);
}
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED
#ifdef RCT_NEW_ARCH_ENABLED
void NativeProxy::commonInit(
jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
&fabricUIManager) {
const auto &uiManager =
fabricUIManager->getBinding()->getScheduler()->getUIManager();
nativeReanimatedModule_->initializeFabric(uiManager);
// removed temporarily, event listener mechanism needs to be fixed on RN side
// eventListener_ = std::make_shared<EventListener>(
// [nativeReanimatedModule,
// getAnimationTimestamp](const RawEvent &rawEvent) {
// return nativeReanimatedModule->handleRawEvent(
// rawEvent, getAnimationTimestamp());
// });
// reactScheduler_ = binding->getScheduler();
// reactScheduler_->addEventListener(eventListener_);
}
#endif // RCT_NEW_ARCH_ENABLED
NativeProxy::~NativeProxy() {
// removed temporary, new event listener mechanism need fix on the RN side
// reactScheduler_->removeEventListener(eventListener_);
// cleanup all animated sensors here, since NativeProxy
// has already been destroyed when AnimatedSensorModule's
// destructor is ran
nativeReanimatedModule_->cleanupSensors();
}
jni::local_ref<NativeProxy::jhybriddata> NativeProxy::initHybrid(
jni::alias_ref<jhybridobject> jThis,
jlong jsContext,
jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>
jsCallInvokerHolder,
jni::alias_ref<AndroidUIScheduler::javaobject> androidUiScheduler,
jni::alias_ref<LayoutAnimations::javaobject> layoutAnimations,
jni::alias_ref<JavaMessageQueueThread::javaobject> messageQueueThread,
#ifdef RCT_NEW_ARCH_ENABLED
jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
fabricUIManager,
#endif
const std::string &valueUnpackerCode) {
auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker();
auto uiScheduler = androidUiScheduler->cthis()->getUIScheduler();
return makeCxxInstance(
jThis,
(jsi::Runtime *)jsContext,
jsCallInvoker,
uiScheduler,
make_global(layoutAnimations),
messageQueueThread,
#ifdef RCT_NEW_ARCH_ENABLED
fabricUIManager,
#endif
valueUnpackerCode);
}
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
jni::local_ref<NativeProxy::jhybriddata> NativeProxy::initHybridBridgeless(
jni::alias_ref<jhybridobject> jThis,
jlong jsContext,
jni::alias_ref<react::JRuntimeExecutor::javaobject> runtimeExecutorHolder,
jni::alias_ref<AndroidUIScheduler::javaobject> androidUiScheduler,
jni::alias_ref<LayoutAnimations::javaobject> layoutAnimations,
jni::alias_ref<JavaMessageQueueThread::javaobject> messageQueueThread,
jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
fabricUIManager,
const std::string &valueUnpackerCode) {
auto uiScheduler = androidUiScheduler->cthis()->getUIScheduler();
auto runtimeExecutor = runtimeExecutorHolder->cthis()->get();
return makeCxxInstance(
jThis,
(jsi::Runtime *)jsContext,
runtimeExecutor,
uiScheduler,
make_global(layoutAnimations),
messageQueueThread,
fabricUIManager,
valueUnpackerCode);
}
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED
#ifndef NDEBUG
void NativeProxy::checkJavaVersion(jsi::Runtime &rnRuntime) {
std::string javaVersion;
try {
javaVersion =
getJniMethod<jstring()>("getReanimatedJavaVersion")(javaPart_.get())
->toStdString();
} catch (std::exception &) {
throw std::runtime_error(
std::string(
"[Reanimated] C++ side failed to resolve Java code version.\n") +
"See `https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#c-side-failed-to-resolve-java-code-version` for more details.");
}
auto cppVersion = getReanimatedCppVersion();
if (cppVersion != javaVersion) {
throw std::runtime_error(
std::string(
"[Reanimated] Mismatch between C++ code version and Java code version (") +
cppVersion + " vs. " + javaVersion + " respectively).\n" +
"See `https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#mismatch-between-c-code-version-and-java-code-version` for more details.");
}
}
void NativeProxy::injectCppVersion() {
auto cppVersion = getReanimatedCppVersion();
try {
static const auto method =
getJniMethod<void(jni::local_ref<JString>)>("setCppVersion");
method(javaPart_.get(), make_jstring(cppVersion));
} catch (std::exception &) {
throw std::runtime_error(
std::string(
"[Reanimated] C++ side failed to resolve Java code version (injection).\n") +
"See `https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#c-side-failed-to-resolve-java-code-version` for more details.");
}
}
#endif // NDEBUG
void NativeProxy::installJSIBindings() {
jsi::Runtime &rnRuntime = *rnRuntime_;
WorkletRuntimeCollector::install(rnRuntime);
auto isReducedMotion = getIsReducedMotion();
RNRuntimeDecorator::decorate(
rnRuntime, nativeReanimatedModule_, isReducedMotion);
#ifndef NDEBUG
checkJavaVersion(rnRuntime);
injectCppVersion();
#endif // NDEBUG
registerEventHandler();
setupLayoutAnimations();
}
bool NativeProxy::isAnyHandlerWaitingForEvent(
const std::string &eventName,
const int emitterReactTag) {
return nativeReanimatedModule_->isAnyHandlerWaitingForEvent(
eventName, emitterReactTag);
}
void NativeProxy::performOperations() {
#ifdef RCT_NEW_ARCH_ENABLED
nativeReanimatedModule_->performOperations();
#endif
}
bool NativeProxy::getIsReducedMotion() {
static const auto method = getJniMethod<jboolean()>("getIsReducedMotion");
return method(javaPart_.get());
}
void NativeProxy::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", NativeProxy::initHybrid),
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
makeNativeMethod(
"initHybridBridgeless", NativeProxy::initHybridBridgeless),
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
makeNativeMethod("installJSIBindings", NativeProxy::installJSIBindings),
makeNativeMethod(
"isAnyHandlerWaitingForEvent",
NativeProxy::isAnyHandlerWaitingForEvent),
makeNativeMethod("performOperations", NativeProxy::performOperations)
});
}
void NativeProxy::requestRender(
std::function<void(double)> onRender,
jsi::Runtime &) {
static const auto method =
getJniMethod<void(AnimationFrameCallback::javaobject)>("requestRender");
method(
javaPart_.get(),
AnimationFrameCallback::newObjectCxxArgs(std::move(onRender)).get());
}
void NativeProxy::registerEventHandler() {
auto eventHandler = bindThis(&NativeProxy::handleEvent);
static const auto method =
getJniMethod<void(EventHandler::javaobject)>("registerEventHandler");
method(
javaPart_.get(),
EventHandler::newObjectCxxArgs(std::move(eventHandler)).get());
}
void NativeProxy::maybeFlushUIUpdatesQueue() {
static const auto method = getJniMethod<void()>("maybeFlushUIUpdatesQueue");
method(javaPart_.get());
}
#ifdef RCT_NEW_ARCH_ENABLED
// nothing
#else
jsi::Value NativeProxy::obtainProp(
jsi::Runtime &rt,
const int viewTag,
const jsi::Value &propName) {
static const auto method =
getJniMethod<jni::local_ref<JString>(int, jni::local_ref<JString>)>(
"obtainProp");
local_ref<JString> propNameJStr =
jni::make_jstring(propName.asString(rt).utf8(rt).c_str());
auto result = method(javaPart_.get(), viewTag, propNameJStr);
std::string str = result->toStdString();
return jsi::Value(rt, jsi::String::createFromAscii(rt, str));
}
void NativeProxy::configureProps(
jsi::Runtime &rt,
const jsi::Value &uiProps,
const jsi::Value &nativeProps) {
static const auto method = getJniMethod<void(
ReadableNativeArray::javaobject, ReadableNativeArray::javaobject)>(
"configureProps");
method(
javaPart_.get(),
ReadableNativeArray::newObjectCxxArgs(jsi::dynamicFromValue(rt, uiProps))
.get(),
ReadableNativeArray::newObjectCxxArgs(
jsi::dynamicFromValue(rt, nativeProps))
.get());
}
void NativeProxy::updateProps(jsi::Runtime &rt, const jsi::Value &operations) {
static const auto method =
getJniMethod<void(int, JMap<JString, JObject>::javaobject)>(
"updateProps");
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);
int viewTag = item.getProperty(rt, "tag").asNumber();
const jsi::Object &props = item.getProperty(rt, "updates").asObject(rt);
method(
javaPart_.get(),
viewTag,
JNIHelper::ConvertToPropsMap(rt, props).get());
}
}
void NativeProxy::scrollTo(int viewTag, double x, double y, bool animated) {
static const auto method =
getJniMethod<void(int, double, double, bool)>("scrollTo");
method(javaPart_.get(), viewTag, x, y, animated);
}
inline jni::local_ref<ReadableArray::javaobject> castReadableArray(
jni::local_ref<ReadableNativeArray::javaobject> const &nativeArray) {
return make_local(
reinterpret_cast<ReadableArray::javaobject>(nativeArray.get()));
}
void NativeProxy::dispatchCommand(
jsi::Runtime &rt,
const int viewTag,
const jsi::Value &commandNameValue,
const jsi::Value &argsValue) {
static const auto method = getJniMethod<void(
int, jni::local_ref<JString>, jni::local_ref<ReadableArray::javaobject>)>(
"dispatchCommand");
local_ref<JString> commandId =
jni::make_jstring(commandNameValue.asString(rt).utf8(rt).c_str());
jni::local_ref<ReadableArray::javaobject> commandArgs =
castReadableArray(ReadableNativeArray::newObjectCxxArgs(
jsi::dynamicFromValue(rt, argsValue)));
method(javaPart_.get(), viewTag, commandId, commandArgs);
}
std::vector<std::pair<std::string, double>> NativeProxy::measure(int viewTag) {
static const auto method =
getJniMethod<local_ref<JArrayFloat>(int)>("measure");
local_ref<JArrayFloat> output = method(javaPart_.get(), viewTag);
size_t size = output->size();
auto elements = output->getRegion(0, size);
return {
{"x", elements[0]},
{"y", elements[1]},
{"pageX", elements[2]},
{"pageY", elements[3]},
{"width", elements[4]},
{"height", elements[5]},
};
}
#endif // RCT_NEW_ARCH_ENABLED
#ifdef RCT_NEW_ARCH_ENABLED
inline jni::local_ref<ReadableMap::javaobject> castReadableMap(
jni::local_ref<ReadableNativeMap::javaobject> const &nativeMap) {
return make_local(reinterpret_cast<ReadableMap::javaobject>(nativeMap.get()));
}
void NativeProxy::synchronouslyUpdateUIProps(
jsi::Runtime &rt,
Tag tag,
const jsi::Object &props) {
static const auto method =
getJniMethod<void(int, jni::local_ref<ReadableMap::javaobject>)>(
"synchronouslyUpdateUIProps");
jni::local_ref<ReadableMap::javaobject> uiProps =
castReadableMap(ReadableNativeMap::newObjectCxxArgs(
jsi::dynamicFromValue(rt, jsi::Value(rt, props))));
method(javaPart_.get(), tag, uiProps);
}
#endif
int NativeProxy::registerSensor(
int sensorType,
int interval,
int,
std::function<void(double[], int)> setter) {
static const auto method =
getJniMethod<int(int, int, SensorSetter::javaobject)>("registerSensor");
return method(
javaPart_.get(),
sensorType,
interval,
SensorSetter::newObjectCxxArgs(std::move(setter)).get());
}
void NativeProxy::unregisterSensor(int sensorId) {
static const auto method = getJniMethod<void(int)>("unregisterSensor");
method(javaPart_.get(), sensorId);
}
void NativeProxy::setGestureState(int handlerTag, int newState) {
static const auto method = getJniMethod<void(int, int)>("setGestureState");
method(javaPart_.get(), handlerTag, newState);
}
int NativeProxy::subscribeForKeyboardEvents(
std::function<void(int, int)> callback,
bool isStatusBarTranslucent) {
static const auto method =
getJniMethod<int(KeyboardWorkletWrapper::javaobject, bool)>(
"subscribeForKeyboardEvents");
return method(
javaPart_.get(),
KeyboardWorkletWrapper::newObjectCxxArgs(std::move(callback)).get(),
isStatusBarTranslucent);
}
void NativeProxy::unsubscribeFromKeyboardEvents(int listenerId) {
static const auto method =
getJniMethod<void(int)>("unsubscribeFromKeyboardEvents");
method(javaPart_.get(), listenerId);
}
double NativeProxy::getAnimationTimestamp() {
static const auto method = getJniMethod<jlong()>("getAnimationTimestamp");
jlong output = method(javaPart_.get());
return static_cast<double>(output);
}
void NativeProxy::handleEvent(
jni::alias_ref<JString> eventName,
jint emitterReactTag,
jni::alias_ref<react::WritableMap> event) {
// handles RCTEvents from RNGestureHandler
if (event.get() == nullptr) {
// Ignore events with null payload.
return;
}
// TODO: convert event directly to jsi::Value without JSON serialization
std::string eventAsString;
try {
eventAsString = event->toString();
} catch (std::exception &) {
// Events from other libraries may contain NaN or INF values which
// cannot be represented in JSON. See
// https://github.com/software-mansion/react-native-reanimated/issues/1776
// for details.
return;
}
#if REACT_NATIVE_MINOR_VERSION >= 72
std::string eventJSON = eventAsString;
#else
// remove "{ NativeMap: " and " }"
std::string eventJSON = eventAsString.substr(13, eventAsString.length() - 15);
#endif
if (eventJSON == "null") {
return;
}
jsi::Runtime &rt = nativeReanimatedModule_->getUIRuntime();
jsi::Value payload;
try {
payload = jsi::Value::createFromJsonUtf8(
rt, reinterpret_cast<uint8_t *>(&eventJSON[0]), eventJSON.size());
} catch (std::exception &) {
// Ignore events with malformed JSON payload.
return;
}
nativeReanimatedModule_->handleEvent(
eventName->toString(), emitterReactTag, payload, getAnimationTimestamp());
}
void NativeProxy::progressLayoutAnimation(
jsi::Runtime &rt,
int tag,
const jsi::Object &newProps,
bool isSharedTransition) {
auto newPropsJNI = JNIHelper::ConvertToPropsMap(rt, newProps);
layoutAnimations_->cthis()->progressLayoutAnimation(
tag, newPropsJNI, isSharedTransition);
}
PlatformDepMethodsHolder NativeProxy::getPlatformDependentMethods() {
#ifdef RCT_NEW_ARCH_ENABLED
// nothing
#else
auto updatePropsFunction = bindThis(&NativeProxy::updateProps);
auto measureFunction = bindThis(&NativeProxy::measure);
auto scrollToFunction = bindThis(&NativeProxy::scrollTo);
auto dispatchCommandFunction = bindThis(&NativeProxy::dispatchCommand);
auto obtainPropFunction = bindThis(&NativeProxy::obtainProp);
#endif
auto getAnimationTimestamp = bindThis(&NativeProxy::getAnimationTimestamp);
auto requestRender = bindThis(&NativeProxy::requestRender);
#ifdef RCT_NEW_ARCH_ENABLED
auto synchronouslyUpdateUIPropsFunction =
bindThis(&NativeProxy::synchronouslyUpdateUIProps);
#else
auto configurePropsFunction = bindThis(&NativeProxy::configureProps);
#endif
auto registerSensorFunction = bindThis(&NativeProxy::registerSensor);
auto unregisterSensorFunction = bindThis(&NativeProxy::unregisterSensor);
auto setGestureStateFunction = bindThis(&NativeProxy::setGestureState);
auto subscribeForKeyboardEventsFunction =
bindThis(&NativeProxy::subscribeForKeyboardEvents);
auto unsubscribeFromKeyboardEventsFunction =
bindThis(&NativeProxy::unsubscribeFromKeyboardEvents);
auto progressLayoutAnimation =
bindThis(&NativeProxy::progressLayoutAnimation);
auto endLayoutAnimation = [this](int tag, bool removeView) {
this->layoutAnimations_->cthis()->endLayoutAnimation(tag, removeView);
};
auto maybeFlushUiUpdatesQueueFunction =
bindThis(&NativeProxy::maybeFlushUIUpdatesQueue);
return {
requestRender,
#ifdef RCT_NEW_ARCH_ENABLED
synchronouslyUpdateUIPropsFunction,
#else
updatePropsFunction,
scrollToFunction,
dispatchCommandFunction,
measureFunction,
configurePropsFunction,
obtainPropFunction,
#endif
getAnimationTimestamp,
progressLayoutAnimation,
endLayoutAnimation,
registerSensorFunction,
unregisterSensorFunction,
setGestureStateFunction,
subscribeForKeyboardEventsFunction,
unsubscribeFromKeyboardEventsFunction,
maybeFlushUiUpdatesQueueFunction,
};
}
void NativeProxy::setupLayoutAnimations() {
auto weakNativeReanimatedModule =
std::weak_ptr<NativeReanimatedModule>(nativeReanimatedModule_);
layoutAnimations_->cthis()->setAnimationStartingBlock(
[weakNativeReanimatedModule](
int tag, int type, alias_ref<JMap<jstring, jstring>> values) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
jsi::Runtime &rt = nativeReanimatedModule->getUIRuntime();
jsi::Object yogaValues(rt);
for (const auto &entry : *values) {
try {
std::string keyString = entry.first->toStdString();
std::string valueString = entry.second->toStdString();
auto key = jsi::String::createFromAscii(rt, keyString);
if (keyString == "currentTransformMatrix" ||
keyString == "targetTransformMatrix") {
jsi::Array matrix =
jsi_utils::convertStringToArray(rt, valueString, 9);
yogaValues.setProperty(rt, key, matrix);
} else {
auto value = stod(valueString);
yogaValues.setProperty(rt, key, value);
}
} catch (std::invalid_argument e) {
throw std::runtime_error(
"[Reanimated] Failed to convert value to number.");
}
}
nativeReanimatedModule->layoutAnimationsManager()
.startLayoutAnimation(
rt, tag, static_cast<LayoutAnimationType>(type), yogaValues);
}
});
layoutAnimations_->cthis()->setHasAnimationBlock(
[weakNativeReanimatedModule](int tag, int type) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
return nativeReanimatedModule->layoutAnimationsManager()
.hasLayoutAnimation(tag, static_cast<LayoutAnimationType>(type));
}
return false;
});
layoutAnimations_->cthis()->setShouldAnimateExitingBlock(
[weakNativeReanimatedModule](int tag, bool shouldAnimate) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
return nativeReanimatedModule->layoutAnimationsManager()
.shouldAnimateExiting(tag, shouldAnimate);
}
return false;
});
#ifndef NDEBUG
layoutAnimations_->cthis()->setCheckDuplicateSharedTag(
[weakNativeReanimatedModule](int viewTag, int screenTag) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
nativeReanimatedModule->layoutAnimationsManager()
.checkDuplicateSharedTag(viewTag, screenTag);
}
});
#endif
layoutAnimations_->cthis()->setClearAnimationConfigBlock(
[weakNativeReanimatedModule](int tag) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
nativeReanimatedModule->layoutAnimationsManager()
.clearLayoutAnimationConfig(tag);
}
});
layoutAnimations_->cthis()->setCancelAnimationForTag(
[weakNativeReanimatedModule](int tag) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
jsi::Runtime &rt = nativeReanimatedModule->getUIRuntime();
nativeReanimatedModule->layoutAnimationsManager()
.cancelLayoutAnimation(rt, tag);
}
});
layoutAnimations_->cthis()->setFindPrecedingViewTagForTransition(
[weakNativeReanimatedModule](int tag) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
return nativeReanimatedModule->layoutAnimationsManager()
.findPrecedingViewTagForTransition(tag);
} else {
return -1;
}
});
}
} // namespace reanimated

View File

@@ -0,0 +1,308 @@
#pragma once
#ifdef RCT_NEW_ARCH_ENABLED
#include <react/fabric/JFabricUIManager.h>
#include <react/renderer/scheduler/Scheduler.h>
#if REACT_NATIVE_MINOR_VERSION >= 74
#include <react/jni/JRuntimeExecutor.h>
#endif // REACT_NATIVE_MINOR_VERSION >= 74
#endif
#include <ReactCommon/CallInvokerHolder.h>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include <react/jni/CxxModuleWrapper.h>
#include <react/jni/JMessageQueueThread.h>
#include <react/jni/JavaScriptExecutorHolder.h>
#include <react/jni/WritableNativeMap.h>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "AndroidUIScheduler.h"
#include "JNIHelper.h"
#include "LayoutAnimations.h"
#include "NativeReanimatedModule.h"
#include "UIScheduler.h"
namespace reanimated {
using namespace facebook;
using namespace facebook::jni;
class AnimationFrameCallback : public HybridClass<AnimationFrameCallback> {
public:
static auto constexpr kJavaDescriptor =
"Lcom/swmansion/reanimated/nativeProxy/AnimationFrameCallback;";
void onAnimationFrame(double timestampMs) {
callback_(timestampMs);
}
static void registerNatives() {
javaClassStatic()->registerNatives({
makeNativeMethod(
"onAnimationFrame", AnimationFrameCallback::onAnimationFrame),
});
}
private:
friend HybridBase;
explicit AnimationFrameCallback(std::function<void(double)> callback)
: callback_(std::move(callback)) {}
std::function<void(double)> callback_;
};
class EventHandler : public HybridClass<EventHandler> {
public:
static auto constexpr kJavaDescriptor =
"Lcom/swmansion/reanimated/nativeProxy/EventHandler;";
void receiveEvent(
jni::alias_ref<JString> eventKey,
jint emitterReactTag,
jni::alias_ref<react::WritableMap> event) {
handler_(eventKey, emitterReactTag, event);
}
static void registerNatives() {
javaClassStatic()->registerNatives({
makeNativeMethod("receiveEvent", EventHandler::receiveEvent),
});
}
private:
friend HybridBase;
explicit EventHandler(std::function<void(
jni::alias_ref<JString>,
jint emitterReactTag,
jni::alias_ref<react::WritableMap>)> handler)
: handler_(std::move(handler)) {}
std::function<
void(jni::alias_ref<JString>, jint, jni::alias_ref<react::WritableMap>)>
handler_;
};
class SensorSetter : public HybridClass<SensorSetter> {
public:
static auto constexpr kJavaDescriptor =
"Lcom/swmansion/reanimated/nativeProxy/SensorSetter;";
void sensorSetter(jni::alias_ref<JArrayFloat> value, int orientationDegrees) {
size_t size = value->size();
auto elements = value->getRegion(0, size);
double array[7];
for (size_t i = 0; i < size; i++) {
array[i] = elements[i];
}
callback_(array, orientationDegrees);
}
static void registerNatives() {
javaClassStatic()->registerNatives({
makeNativeMethod("sensorSetter", SensorSetter::sensorSetter),
});
}
private:
friend HybridBase;
explicit SensorSetter(std::function<void(double[], int)> callback)
: callback_(std::move(callback)) {}
std::function<void(double[], int)> callback_;
};
class KeyboardWorkletWrapper : public HybridClass<KeyboardWorkletWrapper> {
public:
static auto constexpr kJavaDescriptor =
"Lcom/swmansion/reanimated/keyboard/KeyboardWorkletWrapper;";
void invoke(int keyboardState, int height) {
callback_(keyboardState, height);
}
static void registerNatives() {
javaClassStatic()->registerNatives({
makeNativeMethod("invoke", KeyboardWorkletWrapper::invoke),
});
}
private:
friend HybridBase;
explicit KeyboardWorkletWrapper(std::function<void(int, int)> callback)
: callback_(std::move(callback)) {}
std::function<void(int, int)> callback_;
};
class NativeProxy : public jni::HybridClass<NativeProxy> {
public:
static auto constexpr kJavaDescriptor =
"Lcom/swmansion/reanimated/NativeProxy;";
static jni::local_ref<jhybriddata> initHybrid(
jni::alias_ref<jhybridobject> jThis,
jlong jsContext,
jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>
jsCallInvokerHolder,
jni::alias_ref<AndroidUIScheduler::javaobject> androidUiScheduler,
jni::alias_ref<LayoutAnimations::javaobject> layoutAnimations,
jni::alias_ref<JavaMessageQueueThread::javaobject> messageQueueThread,
#ifdef RCT_NEW_ARCH_ENABLED
jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
fabricUIManager,
#endif
const std::string &valueUnpackerCode);
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
static jni::local_ref<jhybriddata> initHybridBridgeless(
jni::alias_ref<jhybridobject> jThis,
jlong jsContext,
jni::alias_ref<react::JRuntimeExecutor::javaobject> runtimeExecutorHolder,
jni::alias_ref<AndroidUIScheduler::javaobject> androidUiScheduler,
jni::alias_ref<LayoutAnimations::javaobject> layoutAnimations,
jni::alias_ref<JavaMessageQueueThread::javaobject> messageQueueThread,
jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
fabricUIManager,
const std::string &valueUnpackerCode);
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED
static void registerNatives();
~NativeProxy();
private:
friend HybridBase;
jni::global_ref<NativeProxy::javaobject> javaPart_;
jsi::Runtime *rnRuntime_;
std::shared_ptr<NativeReanimatedModule> nativeReanimatedModule_;
jni::global_ref<LayoutAnimations::javaobject> layoutAnimations_;
#ifndef NDEBUG
void checkJavaVersion(jsi::Runtime &);
void injectCppVersion();
#endif // NDEBUG
#ifdef RCT_NEW_ARCH_ENABLED
// removed temporarily, event listener mechanism needs to be fixed on RN side
// std::shared_ptr<facebook::react::Scheduler> reactScheduler_;
// std::shared_ptr<EventListener> eventListener_;
#endif
void installJSIBindings();
#ifdef RCT_NEW_ARCH_ENABLED
void synchronouslyUpdateUIProps(
jsi::Runtime &rt,
Tag viewTag,
const jsi::Object &props);
#endif
PlatformDepMethodsHolder getPlatformDependentMethods();
void setupLayoutAnimations();
double getAnimationTimestamp();
bool isAnyHandlerWaitingForEvent(
const std::string &eventName,
const int emitterReactTag);
void performOperations();
bool getIsReducedMotion();
void requestRender(std::function<void(double)> onRender, jsi::Runtime &rt);
void registerEventHandler();
void maybeFlushUIUpdatesQueue();
void setGestureState(int handlerTag, int newState);
int registerSensor(
int sensorType,
int interval,
int iosReferenceFrame,
std::function<void(double[], int)> setter);
void unregisterSensor(int sensorId);
int subscribeForKeyboardEvents(
std::function<void(int, int)> callback,
bool isStatusBarTranslucent);
void unsubscribeFromKeyboardEvents(int listenerId);
#ifdef RCT_NEW_ARCH_ENABLED
// nothing
#else
jsi::Value
obtainProp(jsi::Runtime &rt, const int viewTag, const jsi::Value &propName);
void configureProps(
jsi::Runtime &rt,
const jsi::Value &uiProps,
const jsi::Value &nativeProps);
void updateProps(jsi::Runtime &rt, const jsi::Value &operations);
void scrollTo(int viewTag, double x, double y, bool animated);
void dispatchCommand(
jsi::Runtime &rt,
const int viewTag,
const jsi::Value &commandNameValue,
const jsi::Value &argsValue);
std::vector<std::pair<std::string, double>> measure(int viewTag);
#endif
void handleEvent(
jni::alias_ref<JString> eventName,
jint emitterReactTag,
jni::alias_ref<react::WritableMap> event);
void progressLayoutAnimation(
jsi::Runtime &rt,
int tag,
const jsi::Object &newProps,
bool isSharedTransition);
/***
* Wraps a method of `NativeProxy` in a function object capturing `this`
* @tparam TReturn return type of passed method
* @tparam TParams paramater types of passed method
* @param methodPtr pointer to method to be wrapped
* @return a function object with the same signature as the method, calling
* that method on `this`
*/
template <class TReturn, class... TParams>
std::function<TReturn(TParams...)> bindThis(
TReturn (NativeProxy::*methodPtr)(TParams...)) {
return [this, methodPtr](TParams &&...args) {
return (this->*methodPtr)(std::forward<TParams>(args)...);
};
}
template <class Signature>
JMethod<Signature> getJniMethod(std::string const &methodName) {
return javaPart_->getClass()->getMethod<Signature>(methodName.c_str());
}
explicit NativeProxy(
jni::alias_ref<NativeProxy::jhybridobject> jThis,
jsi::Runtime *rnRuntime,
const std::shared_ptr<facebook::react::CallInvoker> &jsCallInvoker,
const std::shared_ptr<UIScheduler> &uiScheduler,
jni::global_ref<LayoutAnimations::javaobject> layoutAnimations,
jni::alias_ref<JavaMessageQueueThread::javaobject> messageQueueThread,
#ifdef RCT_NEW_ARCH_ENABLED
jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
fabricUIManager,
#endif
const std::string &valueUnpackerCode);
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
explicit NativeProxy(
jni::alias_ref<NativeProxy::jhybridobject> jThis,
jsi::Runtime *rnRuntime,
RuntimeExecutor runtimeExecutor,
const std::shared_ptr<UIScheduler> &uiScheduler,
jni::global_ref<LayoutAnimations::javaobject> layoutAnimations,
jni::alias_ref<JavaMessageQueueThread::javaobject> messageQueueThread,
jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
fabricUIManager,
const std::string &valueUnpackerCode);
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED
#ifdef RCT_NEW_ARCH_ENABLED
void commonInit(jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
&fabricUIManager);
#endif // RCT_NEW_ARCH_ENABLED
};
} // namespace reanimated

View File

@@ -0,0 +1,18 @@
#include <fbjni/fbjni.h>
#include "AndroidUIScheduler.h"
#include "LayoutAnimations.h"
#include "Logger.h"
#include "NativeProxy.h"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
return facebook::jni::initialize(vm, [] {
reanimated::NativeProxy::registerNatives();
reanimated::AnimationFrameCallback::registerNatives();
reanimated::EventHandler::registerNatives();
reanimated::AndroidUIScheduler::registerNatives();
reanimated::LayoutAnimations::registerNatives();
reanimated::SensorSetter::registerNatives();
reanimated::KeyboardWorkletWrapper::registerNatives();
});
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TurboModule.h"
using namespace facebook;
namespace facebook {
namespace react {
TurboModule::TurboModule(
const std::string &name,
const std::shared_ptr<CallInvoker> &jsInvoker)
: name_(name), jsInvoker_(jsInvoker) {}
TurboModule::~TurboModule() {}
jsi::Value TurboModule::get(
jsi::Runtime &runtime,
const jsi::PropNameID &propName) {
std::string propNameUtf8 = propName.utf8(runtime);
auto p = methodMap_.find(propNameUtf8);
if (p == methodMap_.end()) {
// Method was not found, let JS decide what to do.
return jsi::Value::undefined();
}
MethodMetadata meta = p->second;
return jsi::Function::createFromHostFunction(
runtime,
propName,
meta.argCount,
[this, meta](
facebook::jsi::Runtime &rt,
const facebook::jsi::Value &,
const facebook::jsi::Value *args,
size_t count) { return meta.invoker(rt, *this, args, count); });
}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <ReactCommon/CallInvoker.h>
#include <jsi/jsi.h>
#include <memory>
#include <string>
#include <unordered_map>
namespace facebook {
namespace react {
/**
* For now, support the same set of return types as existing impl.
* This can be improved to support richer typed objects.
*/
enum TurboModuleMethodValueKind {
VoidKind,
BooleanKind,
NumberKind,
StringKind,
ObjectKind,
ArrayKind,
FunctionKind,
PromiseKind,
};
/**
* Base HostObject class for every module to be exposed to JS
*/
class JSI_EXPORT TurboModule : public facebook::jsi::HostObject {
public:
TurboModule(
const std::string &name,
const std::shared_ptr<CallInvoker> &jsInvoker);
virtual ~TurboModule();
facebook::jsi::Value get(
facebook::jsi::Runtime &runtime,
const facebook::jsi::PropNameID &propName) override;
const std::string name_;
const std::shared_ptr<CallInvoker> jsInvoker_;
protected:
struct MethodMetadata {
size_t argCount;
facebook::jsi::Value (*invoker)(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count);
};
std::unordered_map<std::string, MethodMetadata> methodMap_;
};
/**
* An app/platform-specific provider function to get an instance of a module
* given a name.
*/
using TurboModuleProviderFunctionType =
std::function<std::shared_ptr<TurboModule>(const std::string &name)>;
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,15 @@
package com.facebook.react.uimanager;
/**
* This class provides a way to workaround limited visibility of
* UIViewOperationQueue#getUIViewOperationQueue. We rely on accessing that method to check if
* operation queue is empty or not. This in turn indicates if we are in a middle of processing batch
* of operations from JS. In such a case we can rely on the enqueued update operations to be flushed
* onto the shadow view hierarchy. Otherwise we want to trigger "dispatchViewUpdates" and enforce
* flush immediately.
*/
public class UIManagerReanimatedHelper {
public static boolean isOperationQueueEmpty(UIImplementation uiImplementation) {
return uiImplementation.getUIViewOperationQueue().isEmpty();
}
}

View File

@@ -0,0 +1,5 @@
package com.swmansion.common;
public interface GestureHandlerStateManager {
void setGestureHandlerState(int handlerTag, int newState);
}

View File

@@ -0,0 +1,51 @@
package com.swmansion.reanimated;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.GuardedRunnable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.UiThreadUtil;
import java.util.concurrent.atomic.AtomicBoolean;
public class AndroidUIScheduler {
@DoNotStrip
@SuppressWarnings("unused")
private final HybridData mHybridData;
private final ReactApplicationContext mContext;
private final AtomicBoolean mActive = new AtomicBoolean(true);
private final Runnable mUIThreadRunnable =
new Runnable() {
@Override
public void run() {
if (mActive.get()) {
triggerUI();
}
}
};
public AndroidUIScheduler(ReactApplicationContext context) {
mHybridData = initHybrid();
mContext = context;
}
private native HybridData initHybrid();
public native void triggerUI();
@DoNotStrip
private void scheduleTriggerOnUI() {
UiThreadUtil.runOnUiThread(
new GuardedRunnable(mContext.getExceptionHandler()) {
public void runGuarded() {
mUIThreadRunnable.run();
}
});
}
public void deactivate() {
mActive.set(false);
}
}

View File

@@ -0,0 +1,43 @@
package com.swmansion.reanimated;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
public class CopiedEvent {
private int targetTag;
private String eventName;
private WritableMap payload;
CopiedEvent(Event event) {
event.dispatch(
new RCTEventEmitter() {
@Override
public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap event) {
CopiedEvent.this.targetTag = targetTag;
CopiedEvent.this.eventName = eventName;
CopiedEvent.this.payload = event.copy();
}
@Override
public void receiveTouches(
String eventName, WritableArray touches, WritableArray changedIndices) {
// noop
}
});
}
public int getTargetTag() {
return targetTag;
}
public String getEventName() {
return eventName;
}
public WritableMap getPayload() {
return payload;
}
}

View File

@@ -0,0 +1,26 @@
package com.swmansion.reanimated;
import com.facebook.react.bridge.JSApplicationCausedNativeException;
import com.facebook.react.bridge.NoSuchKeyException;
import com.facebook.react.bridge.ReadableMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class MapUtils {
public static int getInt(ReadableMap map, @Nonnull String name, String errorMsg) {
try {
return map.getInt(name);
} catch (NoSuchKeyException e) {
throw new JSApplicationCausedNativeException(errorMsg);
}
}
@Nullable
public static String getString(ReadableMap map, @Nonnull String name, String errorMsg) {
try {
return map.getString(name);
} catch (NoSuchKeyException e) {
throw new JSApplicationCausedNativeException(errorMsg);
}
}
}

Some files were not shown because too many files have changed in this diff Show More