Files
Schnappix/node_modules/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp
T

1450 lines
48 KiB
C++

#include <jsi/jsi.h>
#include <reanimated/NativeModules/PropValueProcessor.h>
#include <reanimated/NativeModules/ReanimatedModuleProxy.h>
#include <reanimated/RuntimeDecorators/UIRuntimeDecorator.h>
#include <reanimated/Tools/FeatureFlags.h>
#include <reanimated/Tools/ReanimatedSystraceSection.h>
#include <worklets/Registries/EventHandlerRegistry.h>
#include <worklets/SharedItems/Serializable.h>
#include <worklets/Tools/WorkletEventHandler.h>
#ifdef __ANDROID__
#include <fbjni/fbjni.h>
#endif // __ANDROID__
#include <react/renderer/scheduler/Scheduler.h>
#include <react/renderer/uimanager/UIManagerBinding.h>
#include <functional>
#include <iomanip>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
namespace reanimated {
#if REACT_NATIVE_MINOR_VERSION >= 81
static inline std::shared_ptr<const ShadowNode> shadowNodeFromValue(
jsi::Runtime &rt,
const jsi::Value &shadowNodeWrapper) {
return Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
rt, shadowNodeWrapper);
}
#endif
namespace {
#ifdef ANDROID
constexpr bool shouldUseSynchronousUpdatesInPerformOperations() {
return StaticFeatureFlags::getFlag("ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS");
}
#else
constexpr bool shouldUseSynchronousUpdatesInPerformOperations() {
return false;
}
#endif
std::pair<UpdatesBatch, UpdatesBatch> partitionUpdates(
const UpdatesBatch &updatesBatch,
const std::unordered_set<std::string> &synchronousPropNames,
const bool shouldRequireIntegerColors = false,
const bool allowPartialViews = false) {
UpdatesBatch synchronousUpdatesBatch;
UpdatesBatch shadowTreeUpdatesBatch;
for (const auto &[shadowNode, props] : updatesBatch) {
if (allowPartialViews) {
folly::dynamic synchronousProps = folly::dynamic::object();
folly::dynamic shadowTreeProps = folly::dynamic::object();
for (const auto &[key, value] : props.items()) {
const auto keyStr = key.asString();
const bool isColorProp =
keyStr == "color" || keyStr.find("Color") != std::string::npos;
const bool isSynchronous = synchronousPropNames.contains(keyStr) &&
(!shouldRequireIntegerColors || !isColorProp || value.isInt());
if (isSynchronous) {
synchronousProps[keyStr] = value;
} else {
shadowTreeProps[keyStr] = value;
}
}
if (!synchronousProps.empty()) {
synchronousUpdatesBatch.emplace_back(
shadowNode, std::move(synchronousProps));
}
if (!shadowTreeProps.empty()) {
shadowTreeUpdatesBatch.emplace_back(
shadowNode, std::move(shadowTreeProps));
}
} else {
bool hasOnlySynchronousProps = true;
for (const auto &[key, value] : props.items()) {
const auto keyStr = key.asString();
const bool isColorProp =
keyStr == "color" || keyStr.find("Color") != std::string::npos;
const bool isSynchronous = synchronousPropNames.contains(keyStr) &&
(!shouldRequireIntegerColors || !isColorProp || value.isInt());
if (!isSynchronous) {
hasOnlySynchronousProps = false;
break;
}
}
if (hasOnlySynchronousProps) {
synchronousUpdatesBatch.emplace_back(shadowNode, props);
} else {
shadowTreeUpdatesBatch.emplace_back(shadowNode, props);
}
}
}
return {
std::move(synchronousUpdatesBatch), std::move(shadowTreeUpdatesBatch)};
}
} // namespace
ReanimatedModuleProxy::ReanimatedModuleProxy(
const std::shared_ptr<WorkletsModuleProxy> &workletsModuleProxy,
jsi::Runtime &rnRuntime,
const std::shared_ptr<CallInvoker> &jsCallInvoker,
const PlatformDepMethodsHolder &platformDepMethodsHolder,
const bool isReducedMotion)
: ReanimatedModuleProxySpec(jsCallInvoker),
isReducedMotion_(isReducedMotion),
workletsModuleProxy_(workletsModuleProxy),
eventHandlerRegistry_(std::make_unique<EventHandlerRegistry>()),
requestRender_(platformDepMethodsHolder.requestRender),
animatedSensorModule_(platformDepMethodsHolder),
jsLogger_(
std::make_shared<JSLogger>(workletsModuleProxy->getJSScheduler())),
layoutAnimationsManager_(
std::make_shared<LayoutAnimationsManager>(jsLogger_)),
getAnimationTimestamp_(platformDepMethodsHolder.getAnimationTimestamp),
animatedPropsRegistry_(std::make_shared<AnimatedPropsRegistry>()),
staticPropsRegistry_(std::make_shared<StaticPropsRegistry>()),
updatesRegistryManager_(
std::make_shared<UpdatesRegistryManager>(staticPropsRegistry_)),
viewStylesRepository_(std::make_shared<ViewStylesRepository>(
staticPropsRegistry_,
animatedPropsRegistry_)),
cssAnimationKeyframesRegistry_(
std::make_shared<CSSKeyframesRegistry>(viewStylesRepository_)),
cssAnimationsRegistry_(std::make_shared<CSSAnimationsRegistry>()),
cssTransitionsRegistry_(std::make_shared<CSSTransitionsRegistry>(
staticPropsRegistry_,
getAnimationTimestamp_)),
#ifdef ANDROID
synchronouslyUpdateUIPropsFunction_(
platformDepMethodsHolder.synchronouslyUpdateUIPropsFunction),
#endif // ANDROID
subscribeForKeyboardEventsFunction_(
platformDepMethodsHolder.subscribeForKeyboardEvents),
unsubscribeFromKeyboardEventsFunction_(
platformDepMethodsHolder.unsubscribeFromKeyboardEvents) {
auto lock = updatesRegistryManager_->lock();
// Add registries in order of their priority (from the lowest to the
// highest)
// CSS transitions should be overriden by animated style animations;
// animated style animations should be overriden by CSS animations
updatesRegistryManager_->addRegistry(cssTransitionsRegistry_);
updatesRegistryManager_->addRegistry(animatedPropsRegistry_);
updatesRegistryManager_->addRegistry(cssAnimationsRegistry_);
}
void ReanimatedModuleProxy::init(
const PlatformDepMethodsHolder &platformDepMethodsHolder) {
auto onRenderCallback = [weakThis =
weak_from_this()](const double timestampMs) {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}
strongThis->renderRequested_ = false;
strongThis->onRender(timestampMs);
};
onRenderCallback_ = std::move(onRenderCallback);
auto updateProps = [weakThis = weak_from_this()](
jsi::Runtime &rt, const jsi::Value &operations) {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}
strongThis->animatedPropsRegistry_->update(rt, operations);
};
auto measure = [weakThis = weak_from_this()](
jsi::Runtime &rt,
const jsi::Value &shadowNodeValue) -> jsi::Value {
auto strongThis = weakThis.lock();
if (!strongThis) {
return jsi::Value::undefined();
}
return strongThis->measure(rt, shadowNodeValue);
};
auto dispatchCommand = [weakThis = weak_from_this()](
jsi::Runtime &rt,
const jsi::Value &shadowNodeValue,
const jsi::Value &commandNameValue,
const jsi::Value &argsValue) {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}
strongThis->dispatchCommand(
rt, shadowNodeValue, commandNameValue, argsValue);
};
ProgressLayoutAnimationFunction progressLayoutAnimation =
[weakThis = weak_from_this()](
jsi::Runtime &rt, int tag, const jsi::Object &newStyle) {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}
auto surfaceId =
strongThis->layoutAnimationsProxy_->progressLayoutAnimation(
tag, newStyle);
if (!surfaceId) {
return;
}
strongThis->layoutAnimationFlushRequests_.insert(*surfaceId);
};
auto requestLayoutAnimationRender = [weakThis = weak_from_this()](double) {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}
strongThis->layoutAnimationRenderRequested_ = false;
};
EndLayoutAnimationFunction endLayoutAnimation =
[weakThis = weak_from_this(), requestLayoutAnimationRender](
int tag, bool shouldRemove) {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}
auto surfaceId = strongThis->layoutAnimationsProxy_->endLayoutAnimation(
tag, shouldRemove);
if (!strongThis->layoutAnimationRenderRequested_) {
strongThis->layoutAnimationRenderRequested_ = true;
// if an animation has duration 0, performOperations would not get
// called for it so we call requestRender to have it called in the
// next frame
strongThis->requestRender_(requestLayoutAnimationRender);
}
if (!surfaceId) {
return;
}
strongThis->layoutAnimationFlushRequests_.insert(*surfaceId);
};
auto obtainProp = [weakThis = weak_from_this()](
jsi::Runtime &rt,
const jsi::Value &shadowNodeWrapper,
const jsi::Value &propName) {
auto strongThis = weakThis.lock();
if (!strongThis) {
return jsi::String::createFromUtf8(rt, "");
}
return strongThis->obtainProp(rt, shadowNodeWrapper, propName);
};
jsi::Runtime &uiRuntime =
workletsModuleProxy_->getUIWorkletRuntime()->getJSIRuntime();
UIRuntimeDecorator::decorate(
uiRuntime,
obtainProp,
updateProps,
measure,
dispatchCommand,
platformDepMethodsHolder.getAnimationTimestamp,
platformDepMethodsHolder.setGestureStateFunction,
progressLayoutAnimation,
endLayoutAnimation,
platformDepMethodsHolder.maybeFlushUIUpdatesQueueFunction);
}
ReanimatedModuleProxy::~ReanimatedModuleProxy() {
// 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();
}
jsi::Value ReanimatedModuleProxy::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 handlerSerializable = extractSerializableOrThrow<SerializableWorklet>(
rt,
worklet,
"[Reanimated] Event handler must be a serializable worklet.");
int emitterReactTagInt = emitterReactTag.asNumber();
workletsModuleProxy_->getUIScheduler()->scheduleOnUI(
[=, weakThis = weak_from_this()]() {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}
auto handler = std::make_shared<WorkletEventHandler>(
newRegistrationId,
eventNameStr,
emitterReactTagInt,
handlerSerializable);
strongThis->eventHandlerRegistry_->registerEventHandler(
std::move(handler));
});
return jsi::Value(static_cast<double>(newRegistrationId));
}
void ReanimatedModuleProxy::unregisterEventHandler(
jsi::Runtime &,
const jsi::Value &registrationId) {
uint64_t id = registrationId.asNumber();
workletsModuleProxy_->getUIScheduler()->scheduleOnUI(
[=, weakThis = weak_from_this()]() {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}
strongThis->eventHandlerRegistry_->unregisterEventHandler(id);
});
}
std::string ReanimatedModuleProxy::obtainPropFromShadowNode(
jsi::Runtime &rt,
const std::string &propName,
const std::shared_ptr<const ShadowNode> &shadowNode) {
auto newestCloneOfShadowNode =
uiManager_->getNewestCloneOfShadowNode(*shadowNode);
return PropValueProcessor::processPropValue(
propName, newestCloneOfShadowNode, rt);
}
jsi::Value ReanimatedModuleProxy::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);
workletsModuleProxy_->getUIScheduler()->scheduleOnUI(
[=, weakThis = weak_from_this()]() {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}
jsi::Runtime &uiRuntime =
strongThis->workletsModuleProxy_->getUIWorkletRuntime()
->getJSIRuntime();
const auto resultStr = strongThis->obtainPropFromShadowNode(
uiRuntime, propNameStr, shadowNode);
strongThis->workletsModuleProxy_->getJSScheduler()->scheduleOnJS(
[=](jsi::Runtime &rnRuntime) {
const auto resultValue =
jsi::String::createFromUtf8(rnRuntime, resultStr);
funPtr->call(rnRuntime, resultValue);
});
});
return jsi::Value::undefined();
}
jsi::Value ReanimatedModuleProxy::getStaticFeatureFlag(
jsi::Runtime &rt,
const jsi::Value &name) {
return reanimated::StaticFeatureFlags::getFlag(name.asString(rt).utf8(rt));
}
jsi::Value ReanimatedModuleProxy::setDynamicFeatureFlag(
jsi::Runtime &rt,
const jsi::Value &name,
const jsi::Value &value) {
reanimated::DynamicFeatureFlags::setFlag(
name.asString(rt).utf8(rt), value.asBool());
return jsi::Value::undefined();
}
jsi::Value ReanimatedModuleProxy::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 = extractSerializableOrThrow<SerializableObject>(
rt,
config,
"[Reanimated] Layout animation config must be an object.");
}
}
layoutAnimationsManager_->configureAnimationBatch(batch);
return jsi::Value::undefined();
}
void ReanimatedModuleProxy::setShouldAnimateExiting(
jsi::Runtime &rt,
const jsi::Value &viewTag,
const jsi::Value &shouldAnimate) {
layoutAnimationsManager_->setShouldAnimateExiting(
viewTag.asNumber(), shouldAnimate.getBool());
}
bool ReanimatedModuleProxy::isAnyHandlerWaitingForEvent(
const std::string &eventName,
const int emitterReactTag) {
return eventHandlerRegistry_->isAnyHandlerWaitingForEvent(
eventName, emitterReactTag);
}
void ReanimatedModuleProxy::maybeRequestRender() {
if (!renderRequested_) {
renderRequested_ = true;
requestRender_(onRenderCallback_);
}
}
void ReanimatedModuleProxy::onRender(double timestampMs) {
ReanimatedSystraceSection s("ReanimatedModuleProxy::onRender");
auto callbacks = std::move(frameCallbacks_);
frameCallbacks_.clear();
jsi::Runtime &uiRuntime =
workletsModuleProxy_->getUIWorkletRuntime()->getJSIRuntime();
jsi::Value timestamp{timestampMs};
for (const auto &callback : callbacks) {
runOnRuntimeGuarded(uiRuntime, *callback, timestamp);
}
}
jsi::Value ReanimatedModuleProxy::registerSensor(
jsi::Runtime &rt,
const jsi::Value &sensorType,
const jsi::Value &interval,
const jsi::Value &iosReferenceFrame,
const jsi::Value &sensorDataHandler) {
return animatedSensorModule_.registerSensor(
rt,
workletsModuleProxy_->getUIWorkletRuntime(),
sensorType,
interval,
iosReferenceFrame,
sensorDataHandler);
}
void ReanimatedModuleProxy::unregisterSensor(
jsi::Runtime &,
const jsi::Value &sensorId) {
animatedSensorModule_.unregisterSensor(sensorId);
}
void ReanimatedModuleProxy::cleanupSensors() {
animatedSensorModule_.unregisterAllSensors();
}
void ReanimatedModuleProxy::setViewStyle(
jsi::Runtime &rt,
const jsi::Value &viewTag,
const jsi::Value &viewStyle) {
const auto tag = viewTag.asNumber();
staticPropsRegistry_->set(rt, tag, viewStyle);
if (staticPropsRegistry_->hasObservers(tag)) {
maybeRunCSSLoop();
}
}
void ReanimatedModuleProxy::markNodeAsRemovable(
jsi::Runtime &rt,
const jsi::Value &shadowNodeWrapper) {
auto lock = updatesRegistryManager_->lock();
auto shadowNode = shadowNodeFromValue(rt, shadowNodeWrapper);
updatesRegistryManager_->markNodeAsRemovable(shadowNode);
}
void ReanimatedModuleProxy::unmarkNodeAsRemovable(
jsi::Runtime &rt,
const jsi::Value &viewTag) {
auto lock = updatesRegistryManager_->lock();
updatesRegistryManager_->unmarkNodeAsRemovable(viewTag.asNumber());
}
void ReanimatedModuleProxy::registerCSSKeyframes(
jsi::Runtime &rt,
const jsi::Value &animationName,
const jsi::Value &viewName,
const jsi::Value &keyframesConfig) {
// Convert react view name to Fabric component name
const auto componentName =
componentNameByReactViewName(viewName.asString(rt).utf8(rt));
cssAnimationKeyframesRegistry_->set(
animationName.asString(rt).utf8(rt),
componentName,
parseCSSAnimationKeyframesConfig(
rt, keyframesConfig, componentName, viewStylesRepository_));
}
void ReanimatedModuleProxy::unregisterCSSKeyframes(
jsi::Runtime &rt,
const jsi::Value &animationName,
const jsi::Value &viewName) {
cssAnimationKeyframesRegistry_->remove(
animationName.asString(rt).utf8(rt),
componentNameByReactViewName(viewName.asString(rt).utf8(rt)));
}
void ReanimatedModuleProxy::applyCSSAnimations(
jsi::Runtime &rt,
const jsi::Value &shadowNodeWrapper,
const jsi::Value &animationUpdates) {
auto shadowNode = shadowNodeFromValue(rt, shadowNodeWrapper);
const auto timestamp = getCssTimestamp();
const auto updates = parseCSSAnimationUpdates(rt, animationUpdates);
CSSAnimationsMap newAnimations;
if (!updates.newAnimationSettings.empty()) {
// animationNames always exists when newAnimationSettings is not empty
const auto animationNames = updates.animationNames.value();
const auto animationNamesCount = animationNames.size();
for (const auto &[index, settings] : updates.newAnimationSettings) {
if (index >= animationNamesCount) {
throw std::invalid_argument(
"[Reanimated] index is out of bounds of animationNames");
}
const auto &animationName = animationNames[index];
const auto &keyframesConfig = cssAnimationKeyframesRegistry_->get(
animationName, shadowNode->getComponentName());
newAnimations.emplace(
index,
std::make_shared<CSSAnimation>(
rt,
shadowNode,
animationName,
keyframesConfig,
settings,
timestamp));
}
}
{
auto lock = cssAnimationsRegistry_->lock();
cssAnimationsRegistry_->apply(
rt,
shadowNode,
updates.animationNames,
std::move(newAnimations),
updates.settingsUpdates,
timestamp);
}
maybeRunCSSLoop();
}
void ReanimatedModuleProxy::unregisterCSSAnimations(const jsi::Value &viewTag) {
auto lock = cssAnimationsRegistry_->lock();
cssAnimationsRegistry_->remove(viewTag.asNumber());
}
void ReanimatedModuleProxy::registerCSSTransition(
jsi::Runtime &rt,
const jsi::Value &shadowNodeWrapper,
const jsi::Value &transitionConfig) {
auto shadowNode = shadowNodeFromValue(rt, shadowNodeWrapper);
auto transition = std::make_shared<CSSTransition>(
std::move(shadowNode),
parseCSSTransitionConfig(rt, transitionConfig),
viewStylesRepository_);
{
auto lock = cssTransitionsRegistry_->lock();
cssTransitionsRegistry_->add(transition);
}
maybeRunCSSLoop();
}
void ReanimatedModuleProxy::updateCSSTransition(
jsi::Runtime &rt,
const jsi::Value &viewTag,
const jsi::Value &configUpdates) {
auto lock = cssTransitionsRegistry_->lock();
cssTransitionsRegistry_->updateSettings(
viewTag.asNumber(), parsePartialCSSTransitionConfig(rt, configUpdates));
maybeRunCSSLoop();
}
void ReanimatedModuleProxy::unregisterCSSTransition(
jsi::Runtime &rt,
const jsi::Value &viewTag) {
auto lock = cssTransitionsRegistry_->lock();
cssTransitionsRegistry_->remove(viewTag.asNumber());
}
bool ReanimatedModuleProxy::handleEvent(
const std::string &eventName,
const int emitterReactTag,
const jsi::Value &payload,
double currentTime) {
ReanimatedSystraceSection s("ReanimatedModuleProxy::handleEvent");
eventHandlerRegistry_->processEvent(
workletsModuleProxy_->getUIWorkletRuntime(),
currentTime,
eventName,
emitterReactTag,
payload);
// TODO: return true if Reanimated successfully handled the event
// to avoid sending it to JavaScript
return false;
}
bool ReanimatedModuleProxy::handleRawEvent(
const RawEvent &rawEvent,
double currentTime) {
ReanimatedSystraceSection s("ReanimatedModuleProxy::handleRawEvent");
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);
}
if (!isAnyHandlerWaitingForEvent(eventType, tag)) {
return false;
}
jsi::Runtime &rt =
workletsModuleProxy_->getUIWorkletRuntime()->getJSIRuntime();
const auto &eventPayload = rawEvent.eventPayload;
jsi::Value payload = eventPayload->asJSIValue(rt);
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 ReanimatedModuleProxy::cssLoopCallback(const double /*timestampMs*/) {
shouldUpdateCssAnimations_ = true;
if (cssAnimationsRegistry_->hasUpdates() ||
cssTransitionsRegistry_->hasUpdates()
#ifdef ANDROID
|| updatesRegistryManager_->hasPropsToRevert()
#endif // ANDROID
) {
requestRender_([weakThis = weak_from_this()](const double newTimestampMs) {
if (auto strongThis = weakThis.lock()) {
strongThis->cssLoopCallback(newTimestampMs);
}
});
} else {
cssLoopRunning_ = false;
}
}
void ReanimatedModuleProxy::maybeRunCSSLoop() {
if (cssLoopRunning_) {
return;
}
cssLoopRunning_ = true;
workletsModuleProxy_->getUIScheduler()->scheduleOnUI(
[weakThis = weak_from_this()]() {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}
strongThis->requestRender_([weakThis](const double timestampMs) {
if (auto strongThis = weakThis.lock()) {
strongThis->cssLoopCallback(timestampMs);
}
});
});
}
double ReanimatedModuleProxy::getCssTimestamp() {
if (cssLoopRunning_) {
return currentCssTimestamp_;
}
currentCssTimestamp_ = getAnimationTimestamp_();
return currentCssTimestamp_;
}
void ReanimatedModuleProxy::performOperations() {
ReanimatedSystraceSection s("ReanimatedModuleProxy::performOperations");
auto flushRequestsCopy = std::move(layoutAnimationFlushRequests_);
for (const auto surfaceId : flushRequestsCopy) {
uiManager_->getShadowTreeRegistry().visit(
surfaceId, [](const ShadowTree &shadowTree) {
shadowTree.notifyDelegatesOfUpdates();
});
}
jsi::Runtime &rt =
workletsModuleProxy_->getUIWorkletRuntime()->getJSIRuntime();
UpdatesBatch updatesBatch;
{
ReanimatedSystraceSection s2("ReanimatedModuleProxy::flushUpdates");
auto lock = updatesRegistryManager_->lock();
if (shouldUpdateCssAnimations_) {
currentCssTimestamp_ = getAnimationTimestamp_();
auto lock = cssTransitionsRegistry_->lock();
// Update CSS transitions and flush updates
cssTransitionsRegistry_->update(currentCssTimestamp_);
cssTransitionsRegistry_->flushUpdates(updatesBatch);
}
{
auto lock = animatedPropsRegistry_->lock();
// Flush all animated props updates
animatedPropsRegistry_->flushUpdates(updatesBatch);
}
if (shouldUpdateCssAnimations_) {
auto lock = cssAnimationsRegistry_->lock();
// Update CSS animations and flush updates
cssAnimationsRegistry_->update(currentCssTimestamp_);
cssAnimationsRegistry_->flushUpdates(updatesBatch);
}
shouldUpdateCssAnimations_ = false;
if constexpr (shouldUseSynchronousUpdatesInPerformOperations()) {
applySynchronousUpdates(updatesBatch);
}
if ((updatesBatch.size() > 0) &&
updatesRegistryManager_->shouldReanimatedSkipCommit()) {
updatesRegistryManager_->pleaseCommitAfterPause();
}
}
if (updatesRegistryManager_->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 the updates manager
// which will be applied in ReanimatedCommitHook.
return;
}
commitUpdates(rt, updatesBatch);
// Clear the entire cache after the commit
// (we don't know if the view is updated from outside of Reanimated
// so we have to clear the entire cache)
viewStylesRepository_->clearNodesCache();
}
void ReanimatedModuleProxy::performNonLayoutOperations() {
ReanimatedSystraceSection s(
"ReanimatedModuleProxy::performNonLayoutOperations");
UpdatesBatch updatesBatch = animatedPropsRegistry_->getPendingUpdates();
applySynchronousUpdates(updatesBatch, true);
}
void ReanimatedModuleProxy::applySynchronousUpdates(
UpdatesBatch &updatesBatch,
const bool allowPartialUpdates) {
#ifdef ANDROID
static const std::unordered_set<std::string> synchronousProps = {
"opacity",
"elevation",
"zIndex",
// "shadowOpacity", // not supported on Android
// "shadowRadius", // not supported on Android
"backgroundColor",
// "color", // TODO: fix animating color of Animated.Text,
"tintColor",
"borderRadius",
"borderTopLeftRadius",
"borderTopRightRadius",
"borderTopStartRadius",
"borderTopEndRadius",
"borderBottomLeftRadius",
"borderBottomRightRadius",
"borderBottomStartRadius",
"borderBottomEndRadius",
"borderStartStartRadius",
"borderStartEndRadius",
"borderEndStartRadius",
"borderEndEndRadius",
"borderColor",
"borderTopColor",
"borderBottomColor",
"borderLeftColor",
"borderRightColor",
"borderStartColor",
"borderEndColor",
"transform",
};
// NOTE: Keep in sync with NativeProxy.java
static constexpr auto CMD_START_OF_VIEW = 1;
static constexpr auto CMD_START_OF_TRANSFORM = 2;
static constexpr auto CMD_END_OF_TRANSFORM = 3;
static constexpr auto CMD_END_OF_VIEW = 4;
static constexpr auto CMD_OPACITY = 10;
static constexpr auto CMD_ELEVATION = 11;
static constexpr auto CMD_Z_INDEX = 12;
static constexpr auto CMD_SHADOW_OPACITY = 13;
static constexpr auto CMD_SHADOW_RADIUS = 14;
static constexpr auto CMD_BACKGROUND_COLOR = 15;
static constexpr auto CMD_COLOR = 16;
static constexpr auto CMD_TINT_COLOR = 17;
static constexpr auto CMD_BORDER_RADIUS = 20;
static constexpr auto CMD_BORDER_TOP_LEFT_RADIUS = 21;
static constexpr auto CMD_BORDER_TOP_RIGHT_RADIUS = 22;
static constexpr auto CMD_BORDER_TOP_START_RADIUS = 23;
static constexpr auto CMD_BORDER_TOP_END_RADIUS = 24;
static constexpr auto CMD_BORDER_BOTTOM_LEFT_RADIUS = 25;
static constexpr auto CMD_BORDER_BOTTOM_RIGHT_RADIUS = 26;
static constexpr auto CMD_BORDER_BOTTOM_START_RADIUS = 27;
static constexpr auto CMD_BORDER_BOTTOM_END_RADIUS = 28;
static constexpr auto CMD_BORDER_START_START_RADIUS = 29;
static constexpr auto CMD_BORDER_START_END_RADIUS = 30;
static constexpr auto CMD_BORDER_END_START_RADIUS = 31;
static constexpr auto CMD_BORDER_END_END_RADIUS = 32;
static constexpr auto CMD_BORDER_COLOR = 40;
static constexpr auto CMD_BORDER_TOP_COLOR = 41;
static constexpr auto CMD_BORDER_BOTTOM_COLOR = 42;
static constexpr auto CMD_BORDER_LEFT_COLOR = 43;
static constexpr auto CMD_BORDER_RIGHT_COLOR = 44;
static constexpr auto CMD_BORDER_START_COLOR = 45;
static constexpr auto CMD_BORDER_END_COLOR = 46;
static constexpr auto CMD_TRANSFORM_TRANSLATE_X = 100;
static constexpr auto CMD_TRANSFORM_TRANSLATE_Y = 101;
static constexpr auto CMD_TRANSFORM_SCALE = 102;
static constexpr auto CMD_TRANSFORM_SCALE_X = 103;
static constexpr auto CMD_TRANSFORM_SCALE_Y = 104;
static constexpr auto CMD_TRANSFORM_ROTATE = 105;
static constexpr auto CMD_TRANSFORM_ROTATE_X = 106;
static constexpr auto CMD_TRANSFORM_ROTATE_Y = 107;
static constexpr auto CMD_TRANSFORM_ROTATE_Z = 108;
static constexpr auto CMD_TRANSFORM_SKEW_X = 109;
static constexpr auto CMD_TRANSFORM_SKEW_Y = 110;
static constexpr auto CMD_TRANSFORM_MATRIX = 111;
static constexpr auto CMD_TRANSFORM_PERSPECTIVE = 112;
static constexpr auto CMD_UNIT_DEG = 200;
static constexpr auto CMD_UNIT_RAD = 201;
static constexpr auto CMD_UNIT_PX = 202;
static constexpr auto CMD_UNIT_PERCENT = 203;
const auto propNameToCommand = [](const std::string &name) {
if (name == "opacity")
return CMD_OPACITY;
if (name == "elevation")
return CMD_ELEVATION;
if (name == "zIndex")
return CMD_Z_INDEX;
if (name == "shadowOpacity")
return CMD_SHADOW_OPACITY;
if (name == "shadowRadius")
return CMD_SHADOW_RADIUS;
if (name == "backgroundColor")
return CMD_BACKGROUND_COLOR;
if (name == "color")
return CMD_COLOR;
if (name == "tintColor")
return CMD_TINT_COLOR;
if (name == "borderRadius")
return CMD_BORDER_RADIUS;
if (name == "borderTopLeftRadius")
return CMD_BORDER_TOP_LEFT_RADIUS;
if (name == "borderTopRightRadius")
return CMD_BORDER_TOP_RIGHT_RADIUS;
if (name == "borderTopStartRadius")
return CMD_BORDER_TOP_START_RADIUS;
if (name == "borderTopEndRadius")
return CMD_BORDER_TOP_END_RADIUS;
if (name == "borderBottomLeftRadius")
return CMD_BORDER_BOTTOM_LEFT_RADIUS;
if (name == "borderBottomRightRadius")
return CMD_BORDER_BOTTOM_RIGHT_RADIUS;
if (name == "borderBottomStartRadius")
return CMD_BORDER_BOTTOM_START_RADIUS;
if (name == "borderBottomEndRadius")
return CMD_BORDER_BOTTOM_END_RADIUS;
if (name == "borderStartStartRadius")
return CMD_BORDER_START_START_RADIUS;
if (name == "borderStartEndRadius")
return CMD_BORDER_START_END_RADIUS;
if (name == "borderEndStartRadius")
return CMD_BORDER_END_START_RADIUS;
if (name == "borderEndEndRadius")
return CMD_BORDER_END_END_RADIUS;
if (name == "borderColor")
return CMD_BORDER_COLOR;
if (name == "borderTopColor")
return CMD_BORDER_TOP_COLOR;
if (name == "borderBottomColor")
return CMD_BORDER_BOTTOM_COLOR;
if (name == "borderLeftColor")
return CMD_BORDER_LEFT_COLOR;
if (name == "borderRightColor")
return CMD_BORDER_RIGHT_COLOR;
if (name == "borderStartColor")
return CMD_BORDER_START_COLOR;
if (name == "borderEndColor")
return CMD_BORDER_END_COLOR;
if (name == "transform")
return CMD_START_OF_TRANSFORM; // TODO: use CMD_TRANSFORM?
throw std::runtime_error("[Reanimated] Unsupported style: " + name);
};
const auto transformNameToCommand = [](const std::string &name) {
if (name == "translateX")
return CMD_TRANSFORM_TRANSLATE_X;
if (name == "translateY")
return CMD_TRANSFORM_TRANSLATE_Y;
if (name == "scale")
return CMD_TRANSFORM_SCALE;
if (name == "scaleX")
return CMD_TRANSFORM_SCALE_X;
if (name == "scaleY")
return CMD_TRANSFORM_SCALE_Y;
if (name == "rotate")
return CMD_TRANSFORM_ROTATE;
if (name == "rotateX")
return CMD_TRANSFORM_ROTATE_X;
if (name == "rotateY")
return CMD_TRANSFORM_ROTATE_Y;
if (name == "rotateZ")
return CMD_TRANSFORM_ROTATE_Z;
if (name == "skewX")
return CMD_TRANSFORM_SKEW_X;
if (name == "skewY")
return CMD_TRANSFORM_SKEW_Y;
if (name == "matrix")
return CMD_TRANSFORM_MATRIX;
if (name == "perspective")
return CMD_TRANSFORM_PERSPECTIVE;
throw std::runtime_error("[Reanimated] Unsupported transform: " + name);
};
auto [synchronousUpdatesBatch, shadowTreeUpdatesBatch] = partitionUpdates(
updatesBatch, synchronousProps, true, allowPartialUpdates);
if (!synchronousUpdatesBatch.empty()) {
std::vector<int> intBuffer;
std::vector<double> doubleBuffer;
intBuffer.reserve(1024);
doubleBuffer.reserve(1024);
for (const auto &[shadowNode, props] : synchronousUpdatesBatch) {
intBuffer.push_back(CMD_START_OF_VIEW);
intBuffer.push_back(shadowNode->getTag());
for (const auto &[key, value] : props.items()) {
const auto command = propNameToCommand(key.getString());
switch (command) {
case CMD_OPACITY:
case CMD_ELEVATION:
case CMD_Z_INDEX:
case CMD_SHADOW_OPACITY:
case CMD_SHADOW_RADIUS:
intBuffer.push_back(command);
doubleBuffer.push_back(value.asDouble());
break;
case CMD_BACKGROUND_COLOR:
case CMD_COLOR:
case CMD_TINT_COLOR:
case CMD_BORDER_COLOR:
case CMD_BORDER_TOP_COLOR:
case CMD_BORDER_BOTTOM_COLOR:
case CMD_BORDER_LEFT_COLOR:
case CMD_BORDER_RIGHT_COLOR:
case CMD_BORDER_START_COLOR:
case CMD_BORDER_END_COLOR:
intBuffer.push_back(command);
intBuffer.push_back(value.asInt());
break;
case CMD_BORDER_RADIUS:
case CMD_BORDER_TOP_LEFT_RADIUS:
case CMD_BORDER_TOP_RIGHT_RADIUS:
case CMD_BORDER_TOP_START_RADIUS:
case CMD_BORDER_TOP_END_RADIUS:
case CMD_BORDER_BOTTOM_LEFT_RADIUS:
case CMD_BORDER_BOTTOM_RIGHT_RADIUS:
case CMD_BORDER_BOTTOM_START_RADIUS:
case CMD_BORDER_BOTTOM_END_RADIUS:
case CMD_BORDER_START_START_RADIUS:
case CMD_BORDER_START_END_RADIUS:
case CMD_BORDER_END_START_RADIUS:
case CMD_BORDER_END_END_RADIUS:
intBuffer.push_back(command);
if (value.isDouble()) {
intBuffer.push_back(CMD_UNIT_PX);
doubleBuffer.push_back(value.getDouble());
} else if (value.isString()) {
const auto &valueStr = value.getString();
if (!valueStr.ends_with("%")) {
throw std::runtime_error(
"[Reanimated] Border radius string must be a percentage");
}
intBuffer.push_back(CMD_UNIT_PERCENT);
doubleBuffer.push_back(std::stof(valueStr.substr(0, -1)));
} else {
throw std::runtime_error(
"[Reanimated] Border radius value must be either a number or a string");
}
break;
case CMD_START_OF_TRANSFORM:
intBuffer.push_back(command);
react_native_assert(
value.isArray() &&
"[Reanimated] Transform value must be an array");
for (const auto &item : value) {
react_native_assert(
item.isObject() &&
"[Reanimated] Transform array item must be an object");
react_native_assert(
item.size() == 1 &&
"[Reanimated] Transform array item must have exactly one key-value pair");
const auto transformCommand =
transformNameToCommand(item.keys().begin()->getString());
const auto &transformValue = *item.values().begin();
switch (transformCommand) {
case CMD_TRANSFORM_SCALE:
case CMD_TRANSFORM_SCALE_X:
case CMD_TRANSFORM_SCALE_Y:
case CMD_TRANSFORM_PERSPECTIVE: {
intBuffer.push_back(transformCommand);
doubleBuffer.push_back(transformValue.asDouble());
break;
}
case CMD_TRANSFORM_TRANSLATE_X:
case CMD_TRANSFORM_TRANSLATE_Y: {
intBuffer.push_back(transformCommand);
if (transformValue.isDouble()) {
intBuffer.push_back(CMD_UNIT_PX);
doubleBuffer.push_back(transformValue.getDouble());
} else if (transformValue.isString()) {
const auto &transformValueStr = transformValue.getString();
if (!transformValueStr.ends_with("%")) {
throw std::runtime_error(
"[Reanimated] String translate must be a percentage");
}
intBuffer.push_back(CMD_UNIT_PERCENT);
doubleBuffer.push_back(
std::stof(transformValueStr.substr(0, -1)));
} else {
throw std::runtime_error(
"[Reanimated] Translate value must be either a number or a string");
}
break;
}
case CMD_TRANSFORM_ROTATE:
case CMD_TRANSFORM_ROTATE_X:
case CMD_TRANSFORM_ROTATE_Y:
case CMD_TRANSFORM_ROTATE_Z:
case CMD_TRANSFORM_SKEW_X:
case CMD_TRANSFORM_SKEW_Y: {
const auto &transformValueStr = transformValue.getString();
intBuffer.push_back(transformCommand);
if (transformValueStr.ends_with("deg")) {
intBuffer.push_back(CMD_UNIT_DEG);
} else if (transformValueStr.ends_with("rad")) {
intBuffer.push_back(CMD_UNIT_RAD);
} else {
throw std::runtime_error(
"[Reanimated] Unsupported rotation unit: " +
transformValueStr);
}
doubleBuffer.push_back(
std::stof(transformValueStr.substr(0, -3)));
break;
}
case CMD_TRANSFORM_MATRIX: {
intBuffer.push_back(transformCommand);
react_native_assert(
transformValue.isArray() &&
"[Reanimated] Matrix must be an array");
int size = transformValue.size();
intBuffer.push_back(size);
for (int i = 0; i < size; i++) {
doubleBuffer.push_back(transformValue[i].asDouble());
}
break;
}
}
}
intBuffer.push_back(CMD_END_OF_TRANSFORM);
break;
}
}
intBuffer.push_back(CMD_END_OF_VIEW);
}
synchronouslyUpdateUIPropsFunction_(intBuffer, doubleBuffer);
}
updatesBatch = std::move(shadowTreeUpdatesBatch);
#endif // ANDROID
}
void ReanimatedModuleProxy::requestFlushRegistry() {
requestRender_([weakThis = weak_from_this()](const double timestamp) {
if (auto strongThis = weakThis.lock()) {
strongThis->shouldFlushRegistry_ = true;
}
});
}
void ReanimatedModuleProxy::commitUpdates(
jsi::Runtime &rt,
const UpdatesBatch &updatesBatch) {
ReanimatedSystraceSection s("ReanimatedModuleProxy::commitUpdates");
react_native_assert(uiManager_ != nullptr);
const auto &shadowTreeRegistry = uiManager_->getShadowTreeRegistry();
std::unordered_map<SurfaceId, PropsMap> propsMapBySurface;
#ifdef ANDROID
updatesRegistryManager_->collectPropsToRevertBySurface(propsMapBySurface);
#endif
if (shouldFlushRegistry_) {
shouldFlushRegistry_ = false;
const auto propsMap = updatesRegistryManager_->collectProps();
for (auto const &[family, props] : propsMap) {
const auto surfaceId = family->getSurfaceId();
auto &propsVector = propsMapBySurface[surfaceId][family];
for (const auto &prop : props) {
propsVector.emplace_back(prop);
}
}
} else {
for (auto const &[shadowNode, props] : updatesBatch) {
SurfaceId surfaceId = shadowNode->getSurfaceId();
auto family = &shadowNode->getFamily();
react_native_assert(family->getSurfaceId() == surfaceId);
propsMapBySurface[surfaceId][family].emplace_back(std::move(props));
}
}
for (auto const &[surfaceId, propsMap] : propsMapBySurface) {
shadowTreeRegistry.visit(surfaceId, [&](ShadowTree const &shadowTree) {
const auto status = shadowTree.commit(
[&](RootShadowNode const &oldRootShadowNode)
-> RootShadowNode::Unshared {
if (updatesRegistryManager_->shouldReanimatedSkipCommit()) {
return nullptr;
}
auto rootNode =
cloneShadowTreeWithNewProps(oldRootShadowNode, propsMap);
// Mark the commit as Reanimated commit so that we can distinguish
// it in ReanimatedCommitHook.
auto reaShadowNode =
std::reinterpret_pointer_cast<ReanimatedCommitShadowNode>(
rootNode);
reaShadowNode->setReanimatedCommitTrait();
return rootNode;
},
{/* .enableStateReconciliation = */
false,
/* .mountSynchronously = */ true});
#ifdef ANDROID
if (status == ShadowTree::CommitStatus::Succeeded) {
updatesRegistryManager_->clearPropsToRevert(surfaceId);
}
#endif
});
}
}
void ReanimatedModuleProxy::dispatchCommand(
jsi::Runtime &rt,
const jsi::Value &shadowNodeValue,
const jsi::Value &commandNameValue,
const jsi::Value &argsValue) {
const auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue);
std::string commandName = stringFromValue(rt, commandNameValue);
folly::dynamic args = commandArgsFromValue(rt, argsValue);
const auto &scheduler = static_cast<Scheduler *>(uiManager_->getDelegate());
if (!scheduler) {
return;
}
const auto &schedulerDelegate = scheduler->getDelegate();
if (schedulerDelegate) {
const auto shadowView = ShadowView(*shadowNode);
schedulerDelegate->schedulerDidDispatchCommand(
shadowView, commandName, args);
}
}
jsi::String ReanimatedModuleProxy::obtainProp(
jsi::Runtime &rt,
const jsi::Value &shadowNodeWrapper,
const jsi::Value &propName) {
jsi::Runtime &uiRuntime =
workletsModuleProxy_->getUIWorkletRuntime()->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 ReanimatedModuleProxy::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 ReanimatedModuleProxy::initializeFabric(
const std::shared_ptr<UIManager> &uiManager) {
uiManager_ = uiManager;
viewStylesRepository_->setUIManager(uiManager_);
initializeLayoutAnimationsProxy();
const std::function<void()> request = [weakThis = weak_from_this()]() {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}
strongThis->requestFlushRegistry();
};
mountHook_ = std::make_shared<ReanimatedMountHook>(
uiManager_, updatesRegistryManager_, request);
commitHook_ = std::make_shared<ReanimatedCommitHook>(
uiManager_, updatesRegistryManager_, layoutAnimationsProxy_);
}
void ReanimatedModuleProxy::initializeLayoutAnimationsProxy() {
uiManager_->setAnimationDelegate(nullptr);
auto scheduler = reinterpret_cast<Scheduler *>(uiManager_->getDelegate());
auto componentDescriptorRegistry =
scheduler->getContextContainer()
->at<std::weak_ptr<const ComponentDescriptorRegistry>>(
"ComponentDescriptorRegistry_DO_NOT_USE_PRETTY_PLEASE")
.lock();
if (componentDescriptorRegistry) {
layoutAnimationsProxy_ = std::make_shared<LayoutAnimationsProxy>(
layoutAnimationsManager_,
componentDescriptorRegistry,
scheduler->getContextContainer(),
workletsModuleProxy_->getUIWorkletRuntime()->getJSIRuntime(),
workletsModuleProxy_->getUIScheduler());
}
}
#ifdef IS_REANIMATED_EXAMPLE_APP
std::string format(bool b) {
return b ? "✅" : "❌";
}
std::function<std::string()>
ReanimatedModuleProxy::createRegistriesLeakCheck() {
return [weakThis = weak_from_this()]() {
auto strongThis = weakThis.lock();
if (!strongThis) {
return std::string("");
}
std::string result = "";
result += "AnimatedPropsRegistry: " +
format(strongThis->animatedPropsRegistry_->isEmpty());
result += "\nCSSAnimationsRegistry: " +
format(strongThis->cssAnimationsRegistry_->isEmpty());
result += "\nCSSTransitionsRegistry: " +
format(strongThis->cssTransitionsRegistry_->isEmpty());
result += "\nStaticPropsRegistry: " +
format(strongThis->staticPropsRegistry_->isEmpty()) + "\n";
return result;
};
}
#endif // IS_REANIMATED_EXAMPLE_APP
jsi::Value ReanimatedModuleProxy::subscribeForKeyboardEvents(
jsi::Runtime &rt,
const jsi::Value &handlerWorklet,
const jsi::Value &isStatusBarTranslucent,
const jsi::Value &isNavigationBarTranslucent) {
auto serializableHandler = extractSerializableOrThrow<SerializableWorklet>(
rt,
handlerWorklet,
"[Reanimated] Keyboard event handler must be a worklet.");
return subscribeForKeyboardEventsFunction_(
[=, weakThis = weak_from_this()](int keyboardState, int height) {
auto strongThis = weakThis.lock();
if (!strongThis) {
return;
}
strongThis->workletsModuleProxy_->getUIWorkletRuntime()->runGuarded(
serializableHandler, jsi::Value(keyboardState), jsi::Value(height));
},
isStatusBarTranslucent.getBool(),
isNavigationBarTranslucent.getBool());
}
void ReanimatedModuleProxy::unsubscribeFromKeyboardEvents(
jsi::Runtime &,
const jsi::Value &listenerId) {
unsubscribeFromKeyboardEventsFunction_(listenerId.asNumber());
}
} // namespace reanimated