Skip to content

Commit

Permalink
Update layout props without crossing JS bridge (#45)
Browse files Browse the repository at this point in the history
* Add handling od iOS and Android

* Fix style

* Delete proder.sh

* Fix example

* staph

* example

* example

* Improve iOS

* Optimize android

* Add whitelist ⚪ 📎 📊

* Update REAPropsNode.m

* Fix example

* Update App.js

* Update ConfigHelper.js

* Works

* Update REANodesManager.h

* Add files

* Mor pr issues

* Update Android things

* Revert "Update Android things"

This reverts commit 255bb74

* PR

* Update UIManagerUtils.java

* rename

* rename

* Update UIManagerUtils.java

* Update UIManagerUtils.java

* PR

* Pr stuff

* Update UIManagerReanimatedHelper.java

* revert CAC

* Update Android things

(cherry picked from commit 255bb74)

* Update REAPropsNode.m

* Update ConfigHelper.js

* Update NodesManager.java

* js props

* Update REANodesManager.m

* simplify

* dead

* style

* style

* style

* Update Animated.js

* Update UIManagerReanimatedHelper.java

* Update REANodesManager.m
  • Loading branch information
osdnk authored Aug 14, 2018
1 parent f68b67d commit d54f70e
Show file tree
Hide file tree
Showing 12 changed files with 360 additions and 38 deletions.
2 changes: 2 additions & 0 deletions Example/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Interpolate from './src/interpolate';
import Colors from './colors';
import StartAPI from './startAPI';
import ChatHeads from './chatHeads';
import WidthAndHeight from './widthAndHeight';

YellowBox.ignoreWarnings([
'Warning: isMounted(...) is deprecated',
Expand All @@ -26,6 +27,7 @@ const SCREENS = {
Colors: { screen: Colors, title: 'Colors' },
StartAPI: { screen: StartAPI, title: 'Start API' },
chatHeads: { screen: ChatHeads, title: 'Chat heads (iOS only)' },
width: { screen: WidthAndHeight, title: 'width & height & more' },
};

class MainScreen extends React.Component {
Expand Down
115 changes: 115 additions & 0 deletions Example/widthAndHeight/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';

import Animated, { Easing } from 'react-native-reanimated';

const {
divide,
set,
cond,
startClock,
stopClock,
clockRunning,
block,
spring,
add,
debug,
Value,
Clock,
} = Animated;

function runSpring(clock, value, dest) {
const state = {
finished: new Value(0),
velocity: new Value(0),
position: new Value(0),
time: new Value(0),
};

const config = {
toValue: new Value(0),
damping: 10,
mass: 5,
stiffness: 101.6,
overshootClamping: false,
restSpeedThreshold: 0.001,
restDisplacementThreshold: 0.001,
};

return block([
cond(clockRunning(clock), 0, [
set(state.finished, 0),
set(state.time, 0),
set(state.position, value),
set(state.velocity, -2500),
set(config.toValue, dest),
startClock(clock),
]),
spring(clock, state, config),
cond(state.finished, debug('stop clock', stopClock(clock))),
state.position,
]);
}

export default class Example extends Component {
constructor(props) {
super(props);
const clock = new Clock();
this._trans = runSpring(clock, 10, 150);
}
componentDidMount() {}
render() {
return (
<Animated.View
style={[styles.container, { borderWidth: divide(this._trans, 5) }]}>
<Animated.Text
style={[
styles.box,
{
width: this._trans,
height: this._trans,
},
]}>
sample text is getting bigger and bigger more and moar staph staph
stophhh
</Animated.Text>
<Animated.Text
style={[
styles.text,
{
fontSize: add(divide(this._trans, 10), 15),
letterSpacing: add(divide(this._trans, -15), 10),
},
]}>
aesthetic
</Animated.Text>
<Animated.View style={[styles.box, { top: this._trans }]} />
</Animated.View>
);
}
}

const BOX_SIZE = 100;

const styles = StyleSheet.create({
text: {
justifyContent: 'center',
alignItems: 'center',
color: 'white',
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
borderColor: '#fb628c',
backgroundColor: '#2e13ff',
},
box: {
width: BOX_SIZE,
height: BOX_SIZE,
borderColor: '#f900ff',
alignSelf: 'center',
backgroundColor: '#19ff75',
margin: BOX_SIZE / 2,
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.facebook.react.uimanager;

/**
* This class provides a way to workaround limited visibility of UIViewOperationQueue#getUIViewOperationQueue.
* We rely on accessing that method to check if operation queue is empty or not. This in turn indicates if
* we are in a middle of processing batch of operations from JS. In such a case we can rely on the enqueued update
* operations to be flushed onto the shadow view hierarchy. Otherwise we want to trigger "dispatchViewUpdates" and
* enforce flush immediately.
*/
public class UIManagerReanimatedHelper {
public static boolean isOperationQueueEmpty(UIImplementation uiImplementation) {
return uiImplementation.getUIViewOperationQueue().isEmpty();
}
}
58 changes: 51 additions & 7 deletions android/src/main/java/com/swmansion/reanimated/NodesManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.util.SparseArray;

import com.facebook.react.bridge.GuardedRunnable;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableMap;
Expand All @@ -10,8 +11,10 @@
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.uimanager.GuardedFrameCallback;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.UIImplementation;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.UIManagerReanimatedHelper;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.EventDispatcherListener;
import com.swmansion.reanimated.nodes.AlwaysNode;
Expand All @@ -35,14 +38,14 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.annotation.Nullable;

public class NodesManager implements EventDispatcherListener {

private static final Double ZERO = Double.valueOf(0);
Expand All @@ -60,21 +63,35 @@ public interface OnAnimationFrame {
private final UIManagerModule.CustomEventNamesResolver mCustomEventNamesResolver;
private final AtomicBoolean mCallbackPosted = new AtomicBoolean();
private final NoopNode mNoopNode;
private final ReactContext mContext;
private final UIManagerModule mUIManager;

private List<OnAnimationFrame> mFrameCallbacks = new ArrayList<>();
private ConcurrentLinkedQueue<Event> mEventQueue = new ConcurrentLinkedQueue<>();
private boolean mWantRunUpdates;

public double currentFrameTimeMs;
public final UpdateContext updateContext;
public Set<String> uiProps = Collections.emptySet();
public Set<String> nativeProps = Collections.emptySet();

private final class NativeUpdateOperation {
public int mViewTag;
public WritableMap mNativeProps;
public NativeUpdateOperation(int viewTag, WritableMap nativeProps) {
mViewTag = viewTag;
mNativeProps = nativeProps;
}
}
private Queue<NativeUpdateOperation> mOperationsInBatch = new LinkedList<>();

public NodesManager(ReactContext context) {
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
mContext = context;
mUIManager = context.getNativeModule(UIManagerModule.class);
updateContext = new UpdateContext();
mUIImplementation = uiManager.getUIImplementation();
mCustomEventNamesResolver = uiManager.getDirectEventNamesResolver();
uiManager.getEventDispatcher().addListener(this);
mUIImplementation = mUIManager.getUIImplementation();
mCustomEventNamesResolver = mUIManager.getDirectEventNamesResolver();
mUIManager.getEventDispatcher().addListener(this);

mEventEmitter = context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);

Expand Down Expand Up @@ -137,6 +154,28 @@ private void onAnimationFrame(long frameTimeNanos) {
Node.runUpdates(updateContext);
}

if (!mOperationsInBatch.isEmpty()) {
final Queue<NativeUpdateOperation> copiedOperationsQueue = mOperationsInBatch;
mOperationsInBatch = new LinkedList<>();
mContext.runOnNativeModulesQueueThread(
new GuardedRunnable(mContext) {
@Override
public void runGuarded() {
boolean shouldDispatchUpdates = UIManagerReanimatedHelper.isOperationQueueEmpty(mUIImplementation);
while (!copiedOperationsQueue.isEmpty()) {
NativeUpdateOperation op = copiedOperationsQueue.remove();
ReactShadowNode shadowNode = mUIImplementation.resolveShadowNode(op.mViewTag);
if (shadowNode != null) {
mUIManager.updateView(op.mViewTag, shadowNode.getViewClass(), op.mNativeProps);
}
}
if (shouldDispatchUpdates) {
mUIImplementation.dispatchViewUpdates(-1); // no associated batchId
}
}
});
}

mCallbackPosted.set(false);
mWantRunUpdates = false;

Expand Down Expand Up @@ -284,6 +323,10 @@ public void disconnectNodeFromView(int nodeID, int viewTag) {
((PropsNode) node).disconnectFromView(viewTag);
}

public void enqueueUpdateViewOnNativeThread(int viewTag, WritableMap nativeProps) {
mOperationsInBatch.add(new NativeUpdateOperation(viewTag, nativeProps));
}

public void attachEvent(int viewTag, String eventName, int eventNodeID) {
String key = viewTag + eventName;

Expand All @@ -303,8 +346,9 @@ public void detachEvent(int viewTag, String eventName, int eventNodeID) {
mEventMapping.remove(key);
}

public void configureNativeProps(Set<String> nativePropsSet) {
public void configureProps(Set<String> nativePropsSet, Set<String> uiPropsSet) {
nativeProps = nativePropsSet;
uiProps = uiPropsSet;
}

public void postRunUpdatesAfterAnimation() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,16 +175,22 @@ public void execute(NodesManager nodesManager) {
}

@ReactMethod
public void configureNativeProps(ReadableArray nativePropsArray) {
public void configureProps(ReadableArray nativePropsArray, ReadableArray uiPropsArray) {
int size = nativePropsArray.size();
final Set<String> nativeProps = new HashSet<>(size);
for (int i = 0; i < size; i++) {
nativeProps.add(nativePropsArray.getString(i));
}

size = uiPropsArray.size();
final Set<String> uiProps = new HashSet<>(size);
for (int i = 0; i < size; i++) {
uiProps.add(uiPropsArray.getString(i));
}
mOperations.add(new UIThreadOperation() {
@Override
public void execute(NodesManager nodesManager) {
nodesManager.configureNativeProps(nativeProps);
nodesManager.configureProps(nativeProps, uiProps);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ public void disconnectFromView(int viewTag) {

@Override
protected Double evaluate() {
boolean hasUIProps = false;
boolean hasNativeProps = false;
boolean hasJSProps = false;
WritableMap jsProps = Arguments.createMap();
final WritableMap nativeProps = Arguments.createMap();

for (Map.Entry<String, Integer> entry : mMapping.entrySet()) {
Node node = mNodesManager.findNodeById(entry.getValue(), Node.class);
Expand All @@ -60,9 +62,12 @@ protected Double evaluate() {
while (iter.hasNextKey()) {
String key = iter.nextKey();
WritableMap dest;
if (mNodesManager.nativeProps.contains(key)) {
hasNativeProps = true;
if (mNodesManager.uiProps.contains(key)) {
hasUIProps = true;
dest = mPropMap;
} else if (mNodesManager.nativeProps.contains(key)){
hasNativeProps = true;
dest = nativeProps;
} else {
hasJSProps = true;
dest = jsProps;
Expand All @@ -81,22 +86,25 @@ protected Double evaluate() {
}
} else {
String key = entry.getKey();
if (mNodesManager.nativeProps.contains(key)) {
hasNativeProps = true;
if (mNodesManager.uiProps.contains(key)) {
hasUIProps = true;
mPropMap.putDouble(key, node.doubleValue());
} else {
hasJSProps = true;
jsProps.putDouble(key, node.doubleValue());
hasNativeProps = true;
nativeProps.putDouble(key, node.doubleValue());
}
}
}

if (mConnectedViewTag != View.NO_ID) {
if (hasNativeProps) {
if (hasUIProps) {
mUIImplementation.synchronouslyUpdateViewOnUIThread(
mConnectedViewTag,
mDiffMap);
}
if (hasNativeProps) {
mNodesManager.enqueueUpdateViewOnNativeThread(mConnectedViewTag, nativeProps);
}
if (hasJSProps) {
WritableMap evt = Arguments.createMap();
evt.putInt("viewTag", mConnectedViewTag);
Expand Down
Loading

0 comments on commit d54f70e

Please sign in to comment.