Skip to content

Commit

Permalink
Optimize number of JNI calls by batching updates of non-layout props
Browse files Browse the repository at this point in the history
  • Loading branch information
tomekzaw committed Jan 3, 2023
1 parent 15179df commit a6a1367
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 59 deletions.
7 changes: 2 additions & 5 deletions Common/cpp/Tools/PlatformDepMethodsHolder.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,8 @@ using UpdatePropsFunction = std::function<void(
int viewTag,
const jsi::Value &viewName,
const jsi::Object &object)>;
using UpdateUiPropsFunction = std::function<void(
jsi::Runtime &rt,
int viewTag,
const jsi::Value &viewName,
const jsi::Value &uiProps)>;
using UpdateUiPropsFunction =
std::function<void(jsi::Runtime &rt, const jsi::Value &updates)>;
using UpdateNativePropsFunction = std::function<void(
jsi::Runtime &rt,
int viewTag,
Expand Down
8 changes: 3 additions & 5 deletions Common/cpp/Tools/RuntimeDecorator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,14 +204,12 @@ void RuntimeDecorator::decorateUIRuntime(
const jsi::Value *args,
const size_t count) -> jsi::Value {
SystraceSection s("_updateUiPropsPaper");
const auto viewTag = args[0].asNumber();
const auto &viewName = args[1];
const auto &uiProps = args[2];
updateUiProps(rt, viewTag, viewName, uiProps);
const auto &updates = args[0];
updateUiProps(rt, updates);
return jsi::Value::undefined();
};
jsi::Value updateUiPropsHostFunction = jsi::Function::createFromHostFunction(
rt, jsi::PropNameID::forAscii(rt, "_updateUiPropsPaper"), 2, clbb);
rt, jsi::PropNameID::forAscii(rt, "_updateUiPropsPaper"), 1, clbb);
rt.global().setProperty(rt, "_updateUiPropsPaper", updateUiPropsHostFunction);

auto clbbb = [updateNativeProps](
Expand Down
10 changes: 6 additions & 4 deletions Example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,12 @@ function Circle() {
const animatedStyle = useAnimatedStyle(() => {
return {
// backgroundColor: `hsl(${hue.value}, 100%, 50%)`,
transform: [
{ translateY: top.value - size / 2 },
{ translateX: left.value - size / 2 },
],
// transform: [
// { translateY: top.value - size / 2 },
// { translateX: left.value - size / 2 },
// ],
translateX: left.value - size / 2,
translateY: top.value - size / 2,
opacity: 0.1 + (1 - power) * 0.1,
};
}, []);
Expand Down
25 changes: 9 additions & 16 deletions android/src/main/cpp/NativeProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,10 @@ void NativeProxy::installJSIBindings(
this->updateProps(rt, viewTag, props);
};

auto updateUiPropsFunction = [this](
jsi::Runtime &rt,
int viewTag,
const jsi::Value &viewName,
const jsi::Value &uiProps) {
// viewName is for iOS only, we skip it here
this->updateUiProps(rt, viewTag, uiProps);
};
auto updateUiPropsFunction =
[this](jsi::Runtime &rt, const jsi::Value &updates) {
this->updateUiProps(rt, updates);
};

auto updateNativePropsFunction = [this](
jsi::Runtime &rt,
Expand Down Expand Up @@ -469,18 +465,15 @@ void NativeProxy::updateProps(
javaPart_.get(), viewTag, JNIHelper::ConvertToPropsMap(rt, props).get());
}

void NativeProxy::updateUiProps(
jsi::Runtime &rt,
int viewTag,
const jsi::Value &uiProps) {
void NativeProxy::updateUiProps(jsi::Runtime &rt, const jsi::Value &updates) {
SystraceSection s("NativeProxy::updateUiProps");
static const auto method =
javaPart_->getClass()
->getMethod<void(int, jni::local_ref<ReadableMap::javaobject>)>(
->getMethod<void(jni::local_ref<ReadableMap::javaobject>)>(
"updateUiProps");
jni::local_ref<ReadableMap::javaobject> javaUiProps = castReadableMap(
ReadableNativeMap::newObjectCxxArgs(jsi::dynamicFromValue(rt, uiProps)));
method(javaPart_.get(), viewTag, javaUiProps);
jni::local_ref<ReadableMap::javaobject> javaUpdates = castReadableMap(
ReadableNativeMap::newObjectCxxArgs(jsi::dynamicFromValue(rt, updates)));
method(javaPart_.get(), javaUpdates);
}

void NativeProxy::updateNativeProps(
Expand Down
2 changes: 1 addition & 1 deletion android/src/main/cpp/NativeProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ class NativeProxy : public jni::HybridClass<NativeProxy> {
const jsi::Value &uiProps);
#else
void updateProps(jsi::Runtime &rt, int viewTag, const jsi::Object &props);
void updateUiProps(jsi::Runtime &rt, int viewTag, const jsi::Value &uiProps);
void updateUiProps(jsi::Runtime &rt, const jsi::Value &updates);
void updateNativeProps(
jsi::Runtime &rt,
int viewTag,
Expand Down
13 changes: 10 additions & 3 deletions android/src/main/java/com/swmansion/reanimated/NodesManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.swmansion.reanimated.layoutReanimation.AnimationsManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -377,10 +378,16 @@ public void updateProps(int viewTag, Map<String, Object> props) {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}

public void updateUiProps(int viewTag, ReadableMap uiProps) {
public void updateUiProps(ReadableMap updates) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "NodesManager#updateUiProps");
if (viewTag != View.NO_ID) {
mUIImplementation.synchronouslyUpdateViewOnUIThread(viewTag, new ReactStylesDiffMap(uiProps));
Iterator<Map.Entry<String, Object>> iterator = updates.getEntryIterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = (Map.Entry<String, Object>) iterator.next();
String key = (String) entry.getKey();
int viewTag = Integer.parseInt(key);
Object props = entry.getValue();
ReactStylesDiffMap reactStylesDiffMap = new ReactStylesDiffMap((ReadableMap) props);
mUIImplementation.synchronouslyUpdateViewOnUIThread(viewTag, reactStylesDiffMap);
}
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ private void updateProps(int viewTag, Map<String, Object> props) {
}

@DoNotStrip
private void updateUiProps(int viewTag, ReadableMap uiProps) {
mNodesManager.updateUiProps(viewTag, uiProps);
private void updateUiProps(ReadableMap updates) {
mNodesManager.updateUiProps(updates);
}

@DoNotStrip
Expand Down
35 changes: 28 additions & 7 deletions src/reanimated2/UpdateProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,27 @@ const nativePropsSet = Object.keys({
placeholderTextColor: true,
});

let uiPropUpdates: Record<number, StyleProps | AnimatedStyle> = {}; // tag -> props
const uiPropUpdatesCount: { count: number } = { count: 0 };

function updateUiPropsPaperBatched(
tag: number,
_name: string,
updates: StyleProps | AnimatedStyle
) {
'worklet';
uiPropUpdates[tag] = updates;
++uiPropUpdatesCount.count;
if (uiPropUpdatesCount.count === 1) {
global.setImmediate(() => {
'worklet';
_updateUiPropsPaper(uiPropUpdates);
uiPropUpdates = {};
uiPropUpdatesCount.count = 0;
});
}
}

let updatePropsByPlatform;
if (shouldBeUseWeb()) {
updatePropsByPlatform = (
Expand Down Expand Up @@ -168,17 +189,17 @@ if (shouldBeUseWeb()) {
): void => {
'worklet';

if (_WORKLET) _beginSection('UpdateProps processColor');
// if (_WORKLET) _beginSection('UpdateProps processColor');
for (const key in updates) {
if (ColorProperties.indexOf(key) !== -1) {
if (_WORKLET) _beginSection('processColor');
// if (_WORKLET) _beginSection('processColor');
updates[key] = processColor(updates[key]);
if (_WORKLET) _endSection();
// if (_WORKLET) _endSection();
}
}
if (_WORKLET) _endSection();
// if (_WORKLET) _endSection();

if (_WORKLET) _beginSection('check props');
// if (_WORKLET) _beginSection('check props');
let hasNativeProps = false;
let hasJsProps = false;
for (const key of Object.keys(updates)) {
Expand All @@ -190,12 +211,12 @@ if (shouldBeUseWeb()) {
hasJsProps = true;
}
}
if (_WORKLET) _endSection();
// if (_WORKLET) _endSection();

let nativeUpdatePropsFunction: typeof _updatePropsPaper;
if (!hasNativeProps && !hasJsProps) {
// only UI props
nativeUpdatePropsFunction = _updateUiPropsPaper;
nativeUpdatePropsFunction = updateUiPropsPaperBatched;
} else if (!hasJsProps) {
// only UI or native props
nativeUpdatePropsFunction = _updateNativePropsPaper;
Expand Down
14 changes: 11 additions & 3 deletions src/reanimated2/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ declare global {
updates: StyleProps | AnimatedStyle
) => void;
const _updateUiPropsPaper: (
tag: number,
name: string,
updates: StyleProps | AnimatedStyle
updates: Record<number, StyleProps | AnimatedStyle>
) => void;
const _updateNativePropsPaper: (
tag: number,
Expand Down Expand Up @@ -87,6 +85,7 @@ declare global {
const console: Console;
const _beginSection: (name: string) => void;
const _endSection: () => void;
const _setImmediateFunction: (() => void) | undefined;

namespace NodeJS {
interface Global {
Expand Down Expand Up @@ -117,6 +116,14 @@ declare global {
name: string,
updates: StyleProps | AnimatedStyle
) => void;
_updateUiPropsPaper: (
updates: Record<number, StyleProps | AnimatedStyle>
) => void;
_updateNativePropsPaper: (
tag: number,
name: string,
updates: StyleProps | AnimatedStyle
) => void;
_updatePropsFabric: (
shadowNodeWrapper: ShadowNodeWrapper,
props: StyleProps | AnimatedStyle
Expand Down Expand Up @@ -155,6 +162,7 @@ declare global {
console: Console;
_beginSection: (name: string) => void;
_endSection: () => void;
_setImmediateFunction: (() => void) | undefined;
}
}
}
16 changes: 8 additions & 8 deletions src/reanimated2/hook/useAnimatedStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,15 @@ function styleUpdater(
animationsActive: SharedValue<boolean>
): void {
'worklet';
if (_WORKLET) _beginSection('styleUpdater');
// if (_WORKLET) _beginSection('styleUpdater');

const animations = state.animations ?? {};
if (_WORKLET) _beginSection('updater');
// if (_WORKLET) _beginSection('updater');
const newValues = updater() ?? {};
if (_WORKLET) _endSection();
// if (_WORKLET) _endSection();
const oldValues = state.last;

if (_WORKLET) _beginSection('for loop');
// if (_WORKLET) _beginSection('for loop');
let hasAnimations = false;
for (const key in newValues) {
const value = newValues[key];
Expand All @@ -200,7 +200,7 @@ function styleUpdater(
delete animations[key];
}
}
if (_WORKLET) _endSection();
// if (_WORKLET) _endSection();

if (hasAnimations) {
const frame = (_timestamp?: Timestamp) => {
Expand Down Expand Up @@ -259,16 +259,16 @@ function styleUpdater(
state.isAnimationCancelled = true;
state.animations = [];

if (_WORKLET) _beginSection('styleDiff');
// if (_WORKLET) _beginSection('styleDiff');
const diff = styleDiff(oldValues, newValues);
if (_WORKLET) _endSection();
// if (_WORKLET) _endSection();
state.last = Object.assign({}, oldValues, newValues);
if (diff) {
updateProps(viewDescriptors, newValues, maybeViewRef);
}
}

if (_WORKLET) _endSection();
// if (_WORKLET) _endSection();
}

function jestStyleUpdater(
Expand Down
13 changes: 11 additions & 2 deletions src/reanimated2/initializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ function callGuardDEV<T extends Array<any>, U>(
): void {
'worklet';
try {
if (_WORKLET) _beginSection('callGuardDEV ' + fn.name);
// if (_WORKLET) _beginSection('callGuardDEV ' + fn.name);
fn(...args);
if (_WORKLET) _endSection();
// if (_WORKLET) _endSection();
} catch (e) {
if (global.ErrorUtils) {
global.ErrorUtils.reportFatalError(e as Error);
Expand Down Expand Up @@ -132,6 +132,10 @@ export function initializeUIRuntime() {
const currentCallbacks = callbacks;
callbacks = [];
currentCallbacks.forEach((f) => f(timestamp));
if (global._setImmediateFunction !== undefined) {
global._setImmediateFunction();
global._setImmediateFunction = undefined;
}
});
}
// Reanimated currently does not support cancelling callbacks requested with
Expand All @@ -140,6 +144,11 @@ export function initializeUIRuntime() {
// attempt to store the value returned from rAF and use it for cancelling.
return -1;
};

// @ts-ignore fix types
global.setImmediate = (callback: () => void) => {
global._setImmediateFunction = callback;
};
}
})();
}
6 changes: 3 additions & 3 deletions src/reanimated2/valueSetter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ export function valueSetter(sv: any, value: any): void {
const currentTimestamp = getTimestamp();
initializeAnimation(currentTimestamp);
const step = (timestamp: number) => {
if (_WORKLET) _beginSection('valueSetter step');
// if (_WORKLET) _beginSection('valueSetter step');
if (animation.cancelled) {
animation.callback && animation.callback(false /* finished */);
if (_WORKLET) _endSection();
// if (_WORKLET) _endSection();
return;
}
const finished = animation.onFrame(animation, timestamp);
Expand All @@ -50,7 +50,7 @@ export function valueSetter(sv: any, value: any): void {
} else {
requestAnimationFrame(step);
}
if (_WORKLET) _endSection();
// if (_WORKLET) _endSection();
};

sv._animation = animation;
Expand Down

0 comments on commit a6a1367

Please sign in to comment.