Move cxx module support into oss

Reviewed By: mhorowitz

Differential Revision: D3319751

fbshipit-source-id: 87f91a541cfbbe45bd8561d94f269ba976a9f702
This commit is contained in:
Chris Hopman 2016-05-24 19:24:57 -07:00 committed by Facebook Github Bot 7
parent 04d86d819e
commit a8acf8a5ce
13 changed files with 602 additions and 20 deletions

View File

@ -10,6 +10,10 @@ import os
def react_native_target(path):
return '//ReactAndroid/src/main/' + path
# Example: react_native_target('bridge:bridge')
def react_native_xplat_target(path):
return '//ReactCommon/' + path
# Example: react_native_tests_target('java/com/facebook/react/modules:modules')
def react_native_tests_target(path):
return '//ReactAndroid/src/test/' + path

View File

@ -17,9 +17,9 @@ cxx_library(
'//native/third-party/android-ndk:android',
'//xplat/folly:molly',
'//xplat/fbsystrace:fbsystrace',
'//xplat/react/module:module',
react_native_target('jni/react/jni:jni'),
react_native_xplat_target('bridge:bridge'),
react_native_xplat_target('bridge:module'),
],
srcs = glob(['*.cpp']),
exported_headers = EXPORTED_HEADERS,

View File

@ -9,7 +9,7 @@
#include <jni/LocalString.h>
#include <jni/Registration.h>
#include <Module/JsArgumentHelpers.h>
#include <cxxreact/JsArgumentHelpers.h>
#include <android/log.h>

View File

@ -2,7 +2,7 @@
#pragma once
#include <Module/CxxModule.h>
#include <cxxreact/CxxModule.h>
#include <fb/fbjni.h>
#include <memory>
#include <string>

View File

@ -6,10 +6,9 @@
#include <fb/fbjni.h>
#include <Module/CxxModule.h>
#include <Module/JsArgumentHelpers.h>
#include <cxxreact/CxxModule.h>
#include <cxxreact/Instance.h>
#include <cxxreact/JsArgumentHelpers.h>
#include <cxxreact/NativeModule.h>
#include <react/jni/ReadableNativeArray.h>

View File

@ -1,3 +1,5 @@
include_defs('//ReactAndroid/DEFS')
cxx_library(
name = 'perftests',
srcs = [ 'OnLoad.cpp' ],
@ -9,7 +11,7 @@ cxx_library(
'//native:base',
'//native/fb:fb',
'//xplat/folly:molly',
'//xplat/react/module:module',
react_native_xplat_target('bridge:module'),
],
visibility = [
'//instrumentation_tests/com/facebook/react/...',

View File

@ -2,8 +2,8 @@
#include <fb/log.h>
#include <fb/fbjni.h>
#include <Module/CxxModule.h>
#include <Module/JsArgumentHelpers.h>
#include <cxxreact/CxxModule.h>
#include <cxxreact/JsArgumentHelpers.h>
#include <mutex>
#include <condition_variable>

View File

@ -38,7 +38,6 @@ if THIS_IS_FBANDROID:
)
elif THIS_IS_FBOBJC:
def react_library(**kwargs):
ios_library(
name = 'bridge',
@ -55,20 +54,91 @@ elif THIS_IS_FBOBJC:
)
)
LOCAL_HEADERS = [
'JSCTracing.h',
'JSCLegacyProfiler.h',
'JSCLegacyTracing.h',
'JSCMemory.h',
'JSCPerfStats.h',
]
cxx_library(
name = 'module',
header_namespace = 'cxxreact',
force_static = True,
exported_headers = [
'CxxModule.h',
'JsArgumentHelpers.h',
'JsArgumentHelpers-inl.h',
],
deps = [
'//xplat/folly:molly',
],
visibility = [
'PUBLIC',
],
)
cxx_library(
name = 'samplemodule',
soname = 'libxplat_react_module_samplemodule.so',
srcs = ['SampleCxxModule.cpp'],
exported_headers = ['SampleCxxModule.h'],
header_namespace = '',
compiler_flags = [
'-fno-omit-frame-pointer',
'-Wall',
'-Werror',
'-std=c++11',
'-fexceptions',
],
deps = [
':module',
'//xplat/folly:molly',
],
visibility = [
'PUBLIC',
],
)
react_library(
soname = 'libreactnativefb.so',
header_namespace = 'cxxreact',
force_static = True,
srcs = glob(['*.cpp']),
headers = LOCAL_HEADERS,
srcs = [
'Instance.cpp',
'JSCExecutor.cpp',
'JSCHelpers.cpp',
'JSCLegacyProfiler.cpp',
'JSCLegacyTracing.cpp',
'JSCMemory.cpp',
'JSCPerfStats.cpp',
'JSCTracing.cpp',
'JSCWebWorker.cpp',
'MethodCall.cpp',
'ModuleRegistry.cpp',
'NativeToJsBridge.cpp',
'Platform.cpp',
'Value.cpp',
],
headers = [
'JSCLegacyProfiler.h',
'JSCLegacyTracing.h',
'JSCMemory.h',
'JSCPerfStats.h',
'JSCTracing.h',
],
exported_headers = [
'Executor.h',
'ExecutorToken.h',
'ExecutorTokenFactory.h',
'Instance.h',
'JSCExecutor.h',
'JSCHelpers.h',
'JSCWebWorker.h',
'JSModulesUnbundle.h',
'MessageQueueThread.h',
'MethodCall.h',
'ModuleRegistry.h',
'NativeModule.h',
'NativeToJsBridge.h',
'noncopyable.h',
'Platform.h',
'SystraceSection.h',
'Value.h',
],
preprocessor_flags = [
'-DLOG_TAG="ReactNative"',
'-DWITH_FBSYSTRACE=1',
@ -79,7 +149,6 @@ react_library(
'-fvisibility=hidden',
'-frtti',
],
exported_headers = glob(['*.h'], excludes=LOCAL_HEADERS),
deps = [
'//xplat/fbsystrace:fbsystrace',
],

View File

@ -0,0 +1,132 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#ifndef FBXPLATMODULE
#define FBXPLATMODULE
#include <folly/dynamic.h>
#include <functional>
#include <map>
#include <tuple>
#include <vector>
using namespace std::placeholders;
namespace facebook { namespace xplat { namespace module {
/**
* Base class for Catalyst native modules whose implementations are
* written in C++. Native methods are represented by instances of the
* Method struct. Generally, a derived class will manage an instance
* which represents the data for the module, and non-Catalyst-specific
* methods can be wrapped in lambdas which convert between
* folly::dynamic and native C++ objects. The Callback arguments will
* pass through to js functions passed to the analogous javascript
* methods. At most two callbacks will be converted. Results should
* be passed to the first callback, and errors to the second callback.
* Exceptions thrown by a method will be converted to platform
* exceptions, and handled however they are handled on that platform.
* (TODO mhorowitz #7128529: this exception behavior is not yet
* implemented.)
*
* There are two sets of constructors here. The first set initializes
* a Method using a name and anything convertible to a std::function.
* This is most useful for registering a lambda as a RN method. There
* are overloads to support functions which take no arguments,
* arguments only, and zero, one, or two callbacks.
*
* The second set of methods is similar, but instead of taking a
* function, takes the method name, an object, and a pointer to a
* method on that object.
*/
class CxxModule {
public:
typedef std::function<void(std::vector<folly::dynamic>)> Callback;
struct Method {
std::string name;
size_t callbacks;
std::function<void(folly::dynamic, Callback, Callback)> func;
// std::function/lambda ctors
Method(std::string aname,
std::function<void()>&& afunc)
: name(std::move(aname))
, callbacks(0)
, func(std::bind(std::move(afunc))) {}
Method(std::string aname,
std::function<void(folly::dynamic)>&& afunc)
: name(std::move(aname))
, callbacks(0)
, func(std::bind(std::move(afunc), _1)) {}
Method(std::string aname,
std::function<void(folly::dynamic, Callback)>&& afunc)
: name(std::move(aname))
, callbacks(1)
, func(std::bind(std::move(afunc), _1, _2)) {}
Method(std::string aname,
std::function<void(folly::dynamic, Callback, Callback)>&& afunc)
: name(std::move(aname))
, callbacks(2)
, func(std::move(afunc)) {}
// method pointer ctors
template <typename T>
Method(std::string aname, T* t, void (T::*method)())
: name(std::move(aname))
, callbacks(0)
, func(std::bind(method, t)) {}
template <typename T>
Method(std::string aname, T* t, void (T::*method)(folly::dynamic))
: name(std::move(aname))
, callbacks(0)
, func(std::bind(method, t, _1)) {}
template <typename T>
Method(std::string aname, T* t, void (T::*method)(folly::dynamic, Callback))
: name(std::move(aname))
, callbacks(1)
, func(std::bind(method, t, _1, _2)) {}
template <typename T>
Method(std::string aname, T* t, void (T::*method)(folly::dynamic, Callback, Callback))
: name(std::move(aname))
, callbacks(2)
, func(std::bind(method, t, _1, _2, _3)) {}
};
/**
* This may block, if necessary to complete cleanup before the
* object is destroyed.
*/
virtual ~CxxModule() {}
/**
* @return the name of this module. This will be the name used to {@code require()} this module
* from javascript.
*/
virtual std::string getName() = 0;
/**
* Each entry in the map will be exported as a property to JS. The
* key is the property name, and the value can be anything.
*/
virtual auto getConstants() -> std::map<std::string, folly::dynamic> = 0;
/**
* @return a list of methods this module exports to JS.
*/
virtual auto getMethods() -> std::vector<Method> = 0;
};
}}}
#endif

View File

@ -0,0 +1,90 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
namespace facebook {
namespace xplat {
namespace detail {
template <typename R, typename M, typename... T>
R jsArg1(const folly::dynamic& arg, M asFoo, const T&... desc) {
try {
return (arg.*asFoo)();
} catch (const folly::TypeError& ex) {
throw JsArgumentException(
folly::to<std::string>(
"Error converting javascript arg ", desc..., " to C++: ", ex.what()));
} catch (const std::range_error& ex) {
throw JsArgumentException(
folly::to<std::string>(
"Could not convert argument ", desc..., " to required type: ", ex.what()));
}
}
}
template <typename R, typename... T>
R jsArg(const folly::dynamic& arg, R (folly::dynamic::*asFoo)() const, const T&... desc) {
return detail::jsArg1<R>(arg, asFoo, desc...);
}
template <typename R, typename... T>
R jsArg(const folly::dynamic& arg, R (folly::dynamic::*asFoo)() const&, const T&... desc) {
return detail::jsArg1<R>(arg, asFoo, desc...);
}
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsDynamic(T&& args, size_t n) {
try {
return args[n];
} catch (const std::out_of_range& ex) {
// Use 1-base counting for argument description.
throw JsArgumentException(
folly::to<std::string>(
"JavaScript provided ", args.size(),
" arguments for C++ method which references at least ", n + 1,
" arguments: ", ex.what()));
}
}
template <typename R>
R jsArgN(const folly::dynamic& args, size_t n, R (folly::dynamic::*asFoo)() const) {
return jsArg(jsArgAsDynamic(args, n), asFoo, n);
}
template <typename R>
R jsArgN(const folly::dynamic& args, size_t n, R (folly::dynamic::*asFoo)() const&) {
return jsArg(jsArgAsDynamic(args, n), asFoo, n);
}
namespace detail {
// This is a helper for jsArgAsArray and jsArgAsObject.
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsType(T&& args, size_t n, const char* required,
bool (folly::dynamic::*isFoo)() const) {
T& ret = jsArgAsDynamic(args, n);
if ((ret.*isFoo)()) {
return ret;
}
// Use 1-base counting for argument description.
throw JsArgumentException(
folly::to<std::string>(
"Argument ", n + 1, " of type ", ret.typeName(), " is not required type ", required));
}
} // end namespace detail
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsArray(T&& args, size_t n) {
return detail::jsArgAsType(args, n, "Array", &folly::dynamic::isArray);
}
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsObject(T&& args, size_t n) {
return detail::jsArgAsType(args, n, "Object", &folly::dynamic::isObject);
}
}}

View File

@ -0,0 +1,105 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <folly/Conv.h>
#include <folly/dynamic.h>
#include <exception>
// When building a cross-platform module for React Native, arguments passed
// from JS are represented as a folly::dynamic. This class provides helpers to
// extract arguments from the folly::dynamic to concrete types usable by
// cross-platform code, and converting exceptions to a JsArgumentException so
// they can be caught and reported to RN consistently. The goal is to make the
// jsArgAs... methods at the end simple to use should be most common, but any
// non-detail method can be used when needed.
namespace facebook {
namespace xplat {
class JsArgumentException : public std::logic_error {
public:
JsArgumentException(const std::string& msg) : std::logic_error(msg) {}
};
// This extracts a single argument by calling the given method pointer on it.
// If an exception is thrown, the additional arguments are passed to
// folly::to<> to be included in the exception string. This will be most
// commonly used when extracting values from non-scalar argument. The second
// overload accepts ref-qualified member functions.
template <typename R, typename... T>
R jsArg(const folly::dynamic& arg, R (folly::dynamic::*asFoo)() const, const T&... desc);
template <typename R, typename... T>
R jsArg(const folly::dynamic& arg, R (folly::dynamic::*asFoo)() const&, const T&... desc);
// This is like jsArg, but a operates on a dynamic representing an array of
// arguments. The argument n is used both to index the array and build the
// exception message, if any. It can be used directly, but will more often be
// used by the type-specific methods following.
template <typename R>
R jsArgN(const folly::dynamic& args, size_t n, R (folly::dynamic::*asFoo)() const);
template <typename R>
R jsArgN(const folly::dynamic& args, size_t n, R (folly::dynamic::*asFoo)() const&);
namespace detail {
// This is a type helper to implement functions which should work on both const
// and non-const folly::dynamic arguments, and return a type with the same
// constness. Basically, it causes the templates which use it to be defined
// only for types compatible with folly::dynamic.
template <typename T>
struct is_dynamic {
typedef typename std::enable_if<std::is_assignable<folly::dynamic, T>::value, T>::type type;
};
} // end namespace detail
// Easy to use conversion helpers are here:
// Extract the n'th arg from the given dynamic, as a dynamic. Throws a
// JsArgumentException if there is no n'th arg in the input.
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsDynamic(T&& args, size_t n);
// Extract the n'th arg from the given dynamic, as a dynamic Array. Throws a
// JsArgumentException if there is no n'th arg in the input, or it is not an
// Array.
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsArray(T&& args, size_t n);
// Extract the n'th arg from the given dynamic, as a dynamic Object. Throws a
// JsArgumentException if there is no n'th arg in the input, or it is not an
// Object.
template <typename T>
typename detail::is_dynamic<T>::type& jsArgAsObject(T&& args, size_t n);
// Extract the n'th arg from the given dynamic, as a bool. Throws a
// JsArgumentException if this fails for some reason.
inline bool jsArgAsBool(const folly::dynamic& args, size_t n) {
return jsArgN(args, n, &folly::dynamic::asBool);
}
// Extract the n'th arg from the given dynamic, as an integer. Throws a
// JsArgumentException if this fails for some reason.
inline int64_t jsArgAsInt(const folly::dynamic& args, size_t n) {
return jsArgN(args, n, &folly::dynamic::asInt);
}
// Extract the n'th arg from the given dynamic, as a double. Throws a
// JsArgumentException if this fails for some reason.
inline double jsArgAsDouble(const folly::dynamic& args, size_t n) {
return jsArgN(args, n, &folly::dynamic::asDouble);
}
// Extract the n'th arg from the given dynamic, as a string. Throws a
// JsArgumentException if this fails for some reason.
inline std::string jsArgAsString(const folly::dynamic& args, size_t n) {
return jsArgN(args, n, &folly::dynamic::asString);
}
}}
#include "JsArgumentHelpers-inl.h"

View File

@ -0,0 +1,130 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "SampleCxxModule.h"
#include <cxxreact/JsArgumentHelpers.h>
#include <folly/Memory.h>
#include <glog/logging.h>
#include <thread>
using namespace folly;
namespace facebook { namespace xplat { namespace samples {
std::string Sample::hello() {
LOG(WARNING) << "glog: hello, world";
return "hello";
}
double Sample::add(double a, double b) {
return a + b;
}
std::string Sample::concat(const std::string& a, const std::string& b) {
return a + b;
}
std::string Sample::repeat(int count, const std::string& str) {
std::string ret;
for (int i = 0; i < count; i++) {
ret += str;
}
return ret;
}
void Sample::save(std::map<std::string, std::string> dict)
{
state_ = std::move(dict);
}
std::map<std::string, std::string> Sample::load() {
return state_;
}
void Sample::except() {
// TODO mhorowitz #7128529: There's no way to automatically test this
// right now.
// throw std::runtime_error("oops");
}
void Sample::call_later(int msec, std::function<void()> f) {
std::thread t([=] {
std::this_thread::sleep_for(std::chrono::milliseconds(msec));
f();
});
t.detach();
}
SampleCxxModule::SampleCxxModule(std::unique_ptr<Sample> sample)
: sample_(std::move(sample)) {}
std::string SampleCxxModule::getName() {
return "Sample";
}
auto SampleCxxModule::getConstants() -> std::map<std::string, folly::dynamic> {
return {
{ "one", 1 },
{ "two", 2 },
{ "animal", "fox" },
};
}
auto SampleCxxModule::getMethods() -> std::vector<Method> {
return {
Method("hello", [this] {
sample_->hello();
}),
Method("add", [this](dynamic args, Callback cb) {
LOG(WARNING) << "Sample: add => "
<< sample_->add(jsArgAsDouble(args, 0), jsArgAsDouble(args, 1));
cb({sample_->add(jsArgAsDouble(args, 0), jsArgAsDouble(args, 1))});
}),
Method("concat", [this](dynamic args, Callback cb) {
cb({sample_->concat(jsArgAsString(args, 0),
jsArgAsString(args, 1))});
}),
Method("repeat", [this](dynamic args, Callback cb) {
cb({sample_->repeat(jsArgAsInt(args, 0),
jsArgAsString(args, 1))});
}),
Method("save", this, &SampleCxxModule::save),
Method("load", this, &SampleCxxModule::load),
Method("call_later", [this](dynamic args, Callback cb) {
sample_->call_later(jsArgAsInt(args, 0), [cb] {
cb({});
});
}),
Method("except", [this] {
sample_->except();
}),
};
}
void SampleCxxModule::save(folly::dynamic args) {
std::map<std::string, std::string> m;
for (const auto& p : jsArgN(args, 0, &dynamic::items)) {
m.emplace(jsArg(p.first, &dynamic::asString, "map key"),
jsArg(p.second, &dynamic::asString, "map value"));
}
sample_->save(std::move(m));
}
void SampleCxxModule::load(folly::dynamic args, Callback cb) {
dynamic d = dynamic::object;
for (const auto& p : sample_->load()) {
d.insert(p.first, p.second);
}
cb({d});
}
}}}
// By convention, the function name should be the same as the class
// name.
extern "C" facebook::xplat::module::CxxModule *SampleCxxModule() {
return new facebook::xplat::samples::SampleCxxModule(
folly::make_unique<facebook::xplat::samples::Sample>());
}

View File

@ -0,0 +1,51 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#ifndef FBSAMPLEXPLATMODULE
#define FBSAMPLEXPLATMODULE
#include <cxxreact/CxxModule.h>
#include <memory>
#include <vector>
namespace facebook { namespace xplat { namespace samples {
// In a less contrived example, Sample would be part of a traditional
// C++ library.
class Sample {
public:
std::string hello();
double add(double a, double b);
std::string concat(const std::string& a, const std::string& b);
std::string repeat(int count, const std::string& str);
void save(std::map<std::string, std::string> dict);
std::map<std::string, std::string> load();
void call_later(int msec, std::function<void()> f);
void except();
private:
std::map<std::string, std::string> state_;
};
class SampleCxxModule : public module::CxxModule {
public:
SampleCxxModule(std::unique_ptr<Sample> sample);
std::string getName();
virtual auto getConstants() -> std::map<std::string, folly::dynamic>;
virtual auto getMethods() -> std::vector<Method>;
private:
void save(folly::dynamic args);
void load(folly::dynamic args, Callback cb);
std::unique_ptr<Sample> sample_;
};
}}}
#endif