Introducing Scheduling of JS calls from native

Reviewed By: achen1

Differential Revision: D7729226

fbshipit-source-id: 9869e0a6a2b0c58b7538836ed2c13a4b28dd8887
This commit is contained in:
David Vacca 2018-05-30 21:48:49 -07:00 committed by Facebook Github Bot
parent 4a9b2a7302
commit e61341ba32
7 changed files with 259 additions and 4 deletions

View File

@ -2,7 +2,10 @@ load("//ReactNative:DEFS.bzl", "YOGA_TARGET", "react_native_dep", "react_native_
rn_android_library( rn_android_library(
name = "fabric", name = "fabric",
srcs = glob(["*.java"]), srcs = glob([
"*.java",
"events/*.java",
]),
provided_deps = [ provided_deps = [
react_native_dep("third-party/android/support/v4:lib-support-v4"), react_native_dep("third-party/android/support/v4:lib-support-v4"),
], ],

View File

@ -21,6 +21,7 @@ import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableNativeMap; import com.facebook.react.bridge.ReadableNativeMap;
import com.facebook.react.bridge.UIManager; import com.facebook.react.bridge.UIManager;
import com.facebook.react.common.ReactConstants; import com.facebook.react.common.ReactConstants;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.modules.i18nmanager.I18nUtil; import com.facebook.react.modules.i18nmanager.I18nUtil;
import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.DisplayMetricsHolder;
@ -48,7 +49,7 @@ import javax.annotation.Nullable;
* API. * API.
*/ */
@SuppressWarnings("unused") // used from JNI @SuppressWarnings("unused") // used from JNI
public class FabricUIManager implements UIManager { public class FabricUIManager implements UIManager, JSHandler {
private static final String TAG = FabricUIManager.class.getSimpleName(); private static final String TAG = FabricUIManager.class.getSimpleName();
private static final boolean DEBUG = true; private static final boolean DEBUG = true;
@ -475,4 +476,11 @@ public class FabricUIManager implements UIManager {
throw new RuntimeException(ex.getMessage(), t); throw new RuntimeException(ex.getMessage(), t);
} }
} }
@Override
public void invoke(int instanceHandle, String name, WritableMap params) {
Log.e(TAG, "Invoking '" + name + "' on instanceHandle: '" + instanceHandle + "' from FabricUIManager.");
// -> call to C++
}
} }

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.fabric;
import com.facebook.react.bridge.WritableMap;
public interface JSHandler {
void invoke(int instanceHandle, String name, WritableMap params);
}

View File

@ -0,0 +1,42 @@
package com.facebook.react.fabric;
/**
* This class allows Native code to schedule work in JS.
* Work (following JS naming) represents a task that needs to be executed in JS.
*
* Four types of work are supported by this class:
*
* Synchronous:
* - Sync work -> flushSync
* - Work work -> flushSerial
*
* Asynchronous:
* - Interactive work (serial): -> scheduleSerial
* - Deferred work: -> scheduleWork
*/
public class Scheduler {
public Scheduler() {
}
public void scheduleWork(Work work) {
// TODO T26717866 this method needs to be implemented. The current implementation is just for
// testing purpose.
work.run();
}
public void flushSync(Work work) {
// TODO T26717866 this method needs to be implemented. The current implementation is just for
// testing purpose.
}
public void flushSerial(Work work) {
// TODO T26717866 this method needs to be implemented. The current implementation is just for
// testing purpose.
}
public void scheduleSerial(Work work) {
// TODO T26717866 this method needs to be implemented. The current implementation is just for
// testing purpose.
}
}

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.fabric;
/**
* Interface that represents a task or piece of code that will be executed by {@link Scheduler}
* This follows React API naming for consistency.
*/
public interface Work {
void run();
}

View File

@ -0,0 +1,163 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.fabric.events;
import static com.facebook.react.uimanager.events.TouchesHelper.CHANGED_TOUCHES_KEY;
import static com.facebook.react.uimanager.events.TouchesHelper.TARGET_KEY;
import static com.facebook.react.uimanager.events.TouchesHelper.TOP_TOUCH_CANCEL_KEY;
import static com.facebook.react.uimanager.events.TouchesHelper.TOP_TOUCH_END_KEY;
import static com.facebook.react.uimanager.events.TouchesHelper.TOUCHES_KEY;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.Log;
import android.util.Pair;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.fabric.FabricUIManager;
import com.facebook.react.fabric.Scheduler;
import com.facebook.react.fabric.Work;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Nullable;
@TargetApi(Build.VERSION_CODES.ECLAIR)
public class FabricEventEmitter implements RCTEventEmitter {
private static final String TAG = FabricEventEmitter.class.getSimpleName();
private final FabricUIManager mFabricUIManager;
private final Scheduler mScheduler;
public FabricEventEmitter(Scheduler scheduler, FabricUIManager fabricUIManager) {
mScheduler = scheduler;
mFabricUIManager = fabricUIManager;
}
@Override
public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap params) {
//TODO get instanceHandle associated with targetTag.
int instanceHandle = targetTag;
mScheduler.scheduleWork(new FabricUIManagerWork(instanceHandle, eventName, params) );
}
private class FabricUIManagerWork implements Work {
private final int mInstanceHandle;
private final String mEventName;
private final WritableMap mParams;
public FabricUIManagerWork(int instanceHandle, String eventName, @Nullable WritableMap params) {
mInstanceHandle = instanceHandle;
mEventName = eventName;
mParams = params;
}
@Override
public void run() {
mFabricUIManager.invoke(mInstanceHandle, mEventName, mParams);
}
}
@Override
public void receiveTouches(String eventTopLevelType, WritableArray touches,
WritableArray changedIndices) {
Pair<WritableArray, WritableArray> result =
TOP_TOUCH_END_KEY.equalsIgnoreCase(eventTopLevelType) ||
TOP_TOUCH_CANCEL_KEY.equalsIgnoreCase(eventTopLevelType)
? removeTouchesAtIndices(touches, changedIndices)
: touchSubsequence(touches, changedIndices);
WritableArray changedTouches = result.first;
touches = result.second;
for (int jj = 0; jj < changedTouches.size(); jj++) {
WritableMap touch = getWritableMap(changedTouches.getMap(jj));
// Touch objects can fulfill the role of `DOM` `Event` objects if we set
// the `changedTouches`/`touches`. This saves allocations.
touch.putArray(CHANGED_TOUCHES_KEY, changedTouches);
touch.putArray(TOUCHES_KEY, touches);
WritableMap nativeEvent = touch;
int rootNodeID = 0;
int target = nativeEvent.getInt(TARGET_KEY);
if (target < 1) {
Log.e(TAG,"A view is reporting that a touch occurred on tag zero.");
} else {
rootNodeID = target;
}
receiveEvent(rootNodeID, eventTopLevelType, touch);
}
}
/**
* Destroys `touches` by removing touch objects at indices `indices`. This is
* to maintain compatibility with W3C touch "end" events, where the active
* touches don't include the set that has just been "ended".
*
* This method was originally in ReactNativeRenderer.js
*
* TODO: this method is a copy from ReactNativeRenderer.removeTouchesAtIndices and it needs
* to be rewritten in a more efficient way,
*
* @param touches {@link WritableArray} Deserialized touch objects.
* @param indices {WritableArray} Indices to remove from `touches`.
* @return {Array<Touch>} Subsequence of removed touch objects.
*/
private Pair<WritableArray, WritableArray> removeTouchesAtIndices(WritableArray touches, WritableArray indices) {
WritableArray rippedOut = new WritableNativeArray();
// use an unsafe downcast to alias to nullable elements,
// so we can delete and then compact.
WritableArray tempTouches = new WritableNativeArray();
Set<Integer> rippedOutIndices = new HashSet<>();
for (int i = 0; i < indices.size(); i++) {
int index = indices.getInt(i);
rippedOut.pushMap(getWritableMap(touches.getMap(index)));
rippedOutIndices.add(index);
}
for (int j = 0 ; j < touches.size() ; j++) {
if (!rippedOutIndices.contains(j)) {
tempTouches.pushMap(getWritableMap(touches.getMap(j)));
}
}
return new Pair<>(rippedOut, tempTouches);
}
/**
* Selects a subsequence of `Touch`es, without destroying `touches`.
*
* This method was originally in ReactNativeRenderer.js
*
* @param touches {@link WritableArray} Deserialized touch objects.
* @param changedIndices {@link WritableArray} Indices by which to pull subsequence.
* @return {Array<Touch>} Subsequence of touch objects.
*/
private Pair<WritableArray, WritableArray> touchSubsequence(WritableArray touches, WritableArray changedIndices) {
WritableArray result = new WritableNativeArray();
for (int i = 0; i < changedIndices.size(); i++) {
result.pushMap(getWritableMap(touches.getMap(i)));
}
return new Pair<>(result, touches);
}
/**
* TODO: this is required because the WritableNativeArray.getMap() returns a ReadableMap instead
* of the original writableMap. this will change in the near future.
*
* @param readableMap {@link ReadableMap} source map
*/
private WritableMap getWritableMap(ReadableMap readableMap) {
WritableNativeMap map = new WritableNativeMap();
map.merge(readableMap);
return map;
}
}

View File

@ -17,11 +17,15 @@ import com.facebook.react.uimanager.PixelUtil;
/** /**
* Class responsible for generating catalyst touch events based on android {@link MotionEvent}. * Class responsible for generating catalyst touch events based on android {@link MotionEvent}.
*/ */
/*package*/ class TouchesHelper { public class TouchesHelper {
public static final String TARGET_KEY = "target";
public static final String CHANGED_TOUCHES_KEY = "changedTouches";
public static final String TOUCHES_KEY = "touches";
public static final String TOP_TOUCH_END_KEY = "topTouchEnd";
public static final String TOP_TOUCH_CANCEL_KEY = "topTouchCancel";
private static final String PAGE_X_KEY = "pageX"; private static final String PAGE_X_KEY = "pageX";
private static final String PAGE_Y_KEY = "pageY"; private static final String PAGE_Y_KEY = "pageY";
private static final String TARGET_KEY = "target";
private static final String TIMESTAMP_KEY = "timestamp"; private static final String TIMESTAMP_KEY = "timestamp";
private static final String POINTER_IDENTIFIER_KEY = "identifier"; private static final String POINTER_IDENTIFIER_KEY = "identifier";