1450 lines
48 KiB
C++
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 ®istrationId) {
|
|
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
|