Skip to content

Commit

Permalink
Optimize hot code-paths in uAS (software-mansion#3980)
Browse files Browse the repository at this point in the history
## Summary

This is another attempt at optimizing useAnimatedStyle and style updater
after software-mansion#3950 got reverted. In this approach we omit Object.assign
completely in favor of just using newValues object. Since newValues may
contain animation objects, we use one of the initial loops from the
updater to extract non-animated-props such that they can be used later
on instead of using a call to `filerAnimatedProps`. We also replace
`styleDiff` that'd allocate new object by `shallowEqual` that only does
comparison. We've been using newValues to update the values anyways and
so the diff wasn't used. In addition there was a bug before where we had
`if (diff)` check – this wasn't correct as in case there were no changes
between the objects, the diff would be an empty object and the check
would pass.

## Test plan

Tested on a bunch of different examples. Make sure to verify on examples
that use animations inside uAS.
  • Loading branch information
kmagiera authored and fluiddot committed Jun 5, 2023
1 parent da68a2b commit 693da5e
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 48 deletions.
27 changes: 14 additions & 13 deletions src/reanimated2/hook/useAnimatedStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import NativeReanimatedModule from '../NativeReanimated';
import { useSharedValue } from './useSharedValue';
import {
buildWorkletsHash,
getStyleWithoutAnimations,
isAnimated,
styleDiff,
shallowEqual,
validateAnimatedStyles,
} from './utils';
import { DependencyList, Descriptor } from './commonTypes';
Expand All @@ -30,6 +29,7 @@ import {
BasicWorkletFunctionOptional,
NestedObjectValues,
SharedValue,
StyleProps,
} from '../commonTypes';
export interface AnimatedStyleResult {
viewDescriptors: ViewDescriptorsSet;
Expand Down Expand Up @@ -183,15 +183,19 @@ function styleUpdater(
const animations = state.animations ?? {};
const newValues = updater() ?? {};
const oldValues = state.last;
const nonAnimatedNewValues: StyleProps = {};

let hasAnimations = false;
let hasNonAnimatedValues = false;
for (const key in newValues) {
const value = newValues[key];
if (isAnimated(value)) {
prepareAnimation(value, animations[key], oldValues[key]);
animations[key] = value;
hasAnimations = true;
} else {
hasNonAnimatedValues = true;
nonAnimatedNewValues[key] = value;
delete animations[key];
}
}
Expand Down Expand Up @@ -244,21 +248,19 @@ function styleUpdater(
requestAnimationFrame(frame);
}
}
state.last = Object.assign({}, oldValues, newValues);
const style = getStyleWithoutAnimations(state.last);
if (style) {
updateProps(viewDescriptors, style, maybeViewRef);

if (hasNonAnimatedValues) {
updateProps(viewDescriptors, nonAnimatedNewValues, maybeViewRef);
}
} else {
state.isAnimationCancelled = true;
state.animations = [];

const diff = styleDiff(oldValues, newValues);
state.last = Object.assign({}, oldValues, newValues);
if (diff) {
if (!shallowEqual(oldValues, newValues)) {
updateProps(viewDescriptors, newValues, maybeViewRef);
}
}
state.last = newValues;
}

function jestStyleUpdater(
Expand Down Expand Up @@ -352,13 +354,12 @@ function jestStyleUpdater(
}

// calculate diff
const diff = styleDiff(oldValues, newValues);
state.last = Object.assign({}, oldValues, newValues);
state.last = newValues;

if (Object.keys(diff).length !== 0) {
if (!shallowEqual(oldValues, newValues)) {
updatePropsJestWrapper(
viewDescriptors,
diff,
newValues,
maybeViewRef,
animatedStyle,
adapters
Expand Down
47 changes: 12 additions & 35 deletions src/reanimated2/hook/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
Context,
NativeEvent,
NestedObjectValues,
StyleProps,
WorkletFunction,
AnimationObject,
} from '../commonTypes';
Expand Down Expand Up @@ -178,44 +177,22 @@ export function isAnimated(prop: NestedObjectValues<AnimationObject>): boolean {
return false;
}

export function styleDiff<T extends AnimatedStyle>(
oldStyle: AnimatedStyle,
newStyle: AnimatedStyle
): Partial<T> {
export function shallowEqual(a: any, b: any) {
'worklet';
const diff: any = {};
for (const key in oldStyle) {
if (newStyle[key] === undefined) {
diff[key] = null;
}
}
for (const key in newStyle) {
const value = newStyle[key];
const oldValue = oldStyle[key];

if (isAnimated(value)) {
// do nothing
continue;
}
if (oldValue !== value) {
diff[key] = value;
let aKeys = 0;
for (const key in a) {
aKeys += 1;
if (b[key] === a[key]) {
return false;
}
}
return diff;
}

export function getStyleWithoutAnimations(newStyle: AnimatedStyle): StyleProps {
'worklet';
const diff: StyleProps = {};

for (const key in newStyle) {
const value = newStyle[key];
if (isAnimated(value)) {
continue;
}
diff[key] = value;
// we use for loop here, as we want to avoid calling Object.keys that allocates new array
let bKeys = 0;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const key in b) {
bKeys += 1;
}
return diff;
return aKeys === bKeys;
}

export const validateAnimatedStyles = (styles: AnimatedStyle): void => {
Expand Down

0 comments on commit 693da5e

Please sign in to comment.