Fabric: folly::dynamic was replaced with RawValue in prop-parsing infra

Summary:
Our long-term plan is to completely illuminate `jsi::Value`-to-`folly::dynamic` serialization step in prop parsing process improving performance and memory pressure. At the same time, we don't want to introduce a hard dependency in application code to JSI because it exposes direct access to VM and prevents parsing some data that come *NOT* from JSVM.
RawValue is an extremely light-weight (hopefully fully optimized-out) abstraction that provides limited JSON-like and C++-idiomatic interface.

The current particular implementation is still using `folly::dynamic` inside, but I have fully JSI-powered one which will replace the current one right after we figure out how to deal with folly::dynamic-specific callsites. Or we can implement RawValue in a hybrid manner if a code-size implication of that will be minimal.

Reviewed By: JoshuaGross, mdvacca

Differential Revision: D13962466

fbshipit-source-id: e848522fd242f21e9e771773f2103f1c1d9d7f21
This commit is contained in:
Valentin Shergin 2019-02-06 16:12:45 -08:00 committed by Facebook Github Bot
parent b0c8275369
commit 9842e39019
26 changed files with 660 additions and 319 deletions

View File

@ -171,11 +171,7 @@ local_ref<JMountItem::javaobject> createUpdatePropsMountItem(const jni::global_r
auto newViewProps = *std::dynamic_pointer_cast<const ViewProps>(shadowView.props);
// TODO: move props from map to a typed object.
auto rawProps = shadowView.props->rawProps;
folly::dynamic newProps = folly::dynamic::object();
for (auto element : rawProps) {
newProps[element.first] = element.second;
}
auto newProps = shadowView.props->rawProps;
local_ref<ReadableNativeMap::jhybridobject> readableMap = ReadableNativeMap::newObjectCxxArgs(newProps);

View File

@ -36,8 +36,8 @@ inline std::string toString(const EllipsizeMode &ellipsisMode) {
}
}
inline void fromDynamic(const folly::dynamic &value, EllipsizeMode &result) {
auto string = value.getString();
inline void fromRawValue(const RawValue &value, EllipsizeMode &result) {
auto string = (std::string)value;
if (string == "clip") {
result = EllipsizeMode::Clip;
return;
@ -57,8 +57,8 @@ inline void fromDynamic(const folly::dynamic &value, EllipsizeMode &result) {
abort();
}
inline void fromDynamic(const folly::dynamic &value, FontWeight &result) {
auto string = value.asString();
inline void fromRawValue(const RawValue &value, FontWeight &result) {
auto string = (std::string)value;
if (string == "normal") {
result = FontWeight::Regular;
return;
@ -114,8 +114,8 @@ inline std::string toString(const FontWeight &fontWeight) {
return folly::to<std::string>((int)fontWeight);
}
inline void fromDynamic(const folly::dynamic &value, FontStyle &result) {
auto string = value.asString();
inline void fromRawValue(const RawValue &value, FontStyle &result) {
auto string = (std::string)value;
if (string == "normal") {
result = FontStyle::Normal;
return;
@ -142,28 +142,28 @@ inline std::string toString(const FontStyle &fontStyle) {
}
}
inline void fromDynamic(const folly::dynamic &value, FontVariant &result) {
assert(value.isArray());
inline void fromRawValue(const RawValue &value, FontVariant &result) {
assert(value.hasType<std::vector<std::string>>());
result = FontVariant::Default;
for (auto &&item : value) {
auto string = item.asString();
if (string == "small-caps") {
auto items = std::vector<std::string>{value};
for (const auto &item : items) {
if (item == "small-caps") {
result = (FontVariant)((int)result | (int)FontVariant::SmallCaps);
continue;
}
if (string == "oldstyle-nums") {
if (item == "oldstyle-nums") {
result = (FontVariant)((int)result | (int)FontVariant::OldstyleNums);
continue;
}
if (string == "lining-nums") {
if (item == "lining-nums") {
result = (FontVariant)((int)result | (int)FontVariant::LiningNums);
continue;
}
if (string == "tabular-nums") {
if (item == "tabular-nums") {
result = (FontVariant)((int)result | (int)FontVariant::TabularNums);
continue;
}
if (string == "proportional-nums") {
if (item == "proportional-nums") {
result = (FontVariant)((int)result | (int)FontVariant::ProportionalNums);
continue;
}
@ -196,8 +196,8 @@ inline std::string toString(const FontVariant &fontVariant) {
return result;
}
inline void fromDynamic(const folly::dynamic &value, TextAlignment &result) {
auto string = value.asString();
inline void fromRawValue(const RawValue &value, TextAlignment &result) {
auto string = (std::string)value;
if (string == "natural") {
result = TextAlignment::Natural;
return;
@ -236,8 +236,8 @@ inline std::string toString(const TextAlignment &textAlignment) {
}
}
inline void fromDynamic(const folly::dynamic &value, WritingDirection &result) {
auto string = value.asString();
inline void fromRawValue(const RawValue &value, WritingDirection &result) {
auto string = (std::string)value;
if (string == "natural") {
result = WritingDirection::Natural;
return;
@ -264,10 +264,10 @@ inline std::string toString(const WritingDirection &writingDirection) {
}
}
inline void fromDynamic(
const folly::dynamic &value,
inline void fromRawValue(
const RawValue &value,
TextDecorationLineType &result) {
auto string = value.asString();
auto string = (std::string)value;
if (string == "none") {
result = TextDecorationLineType::None;
return;
@ -301,10 +301,10 @@ inline std::string toString(
}
}
inline void fromDynamic(
const folly::dynamic &value,
inline void fromRawValue(
const RawValue &value,
TextDecorationLineStyle &result) {
auto string = value.asString();
auto string = (std::string)value;
if (string == "single") {
result = TextDecorationLineStyle::Single;
return;
@ -332,10 +332,10 @@ inline std::string toString(
}
}
inline void fromDynamic(
const folly::dynamic &value,
inline void fromRawValue(
const RawValue &value,
TextDecorationLinePattern &result) {
auto string = value.asString();
auto string = (std::string)value;
if (string == "solid") {
result = TextDecorationLinePattern::Solid;
return;

View File

@ -13,10 +13,10 @@
namespace facebook {
namespace react {
inline void fromDynamic(
const folly::dynamic &value,
inline void fromRawValue(
const RawValue &value,
ActivityIndicatorViewSize &result) {
auto string = value.asString();
auto string = (std::string)value;
if (string == "large") {
result = ActivityIndicatorViewSize::Large;
return;

View File

@ -14,41 +14,43 @@
namespace facebook {
namespace react {
inline void fromDynamic(const folly::dynamic &value, ImageSource &result) {
if (value.isString()) {
result = {.type = ImageSource::Type::Remote, .uri = value.asString()};
inline void fromRawValue(const RawValue &value, ImageSource &result) {
if (value.hasType<std::string>()) {
result = {.type = ImageSource::Type::Remote, .uri = (std::string)value};
return;
}
if (value.isObject()) {
if (value.hasType<std::unordered_map<std::string, RawValue>>()) {
auto items = (std::unordered_map<std::string, RawValue>)value;
result = {};
result.type = ImageSource::Type::Remote;
if (value.count("__packager_asset")) {
if (items.find("__packager_asset") != items.end()) {
result.type = ImageSource::Type::Local;
}
if (value.count("width") && value.count("height")) {
fromDynamic(value, result.size);
if (items.find("width") != items.end() &&
items.find("height") != items.end()) {
result.size = {(Float)items.at("width"), (Float)items.at("height")};
}
if (value.count("scale")) {
result.scale = (Float)value["scale"].asDouble();
if (items.find("scale") != items.end()) {
result.scale = (Float)items.at("scale");
} else {
result.scale = value.count("deprecated") ? 0.0 : 1.0;
result.scale = items.find("deprecated") != items.end() ? 0.0 : 1.0;
}
if (value.count("url")) {
result.uri = value["url"].asString();
if (items.find("url") != items.end()) {
result.uri = (std::string)items.at("url");
}
if (value.count("uri")) {
result.uri = value["uri"].asString();
if (items.find("uri") != items.end()) {
result.uri = (std::string)items.at("uri");
}
if (value.count("bundle")) {
result.bundle = value["bundle"].asString();
if (items.find("bundle") != items.end()) {
result.bundle = (std::string)items.at("bundle");
result.type = ImageSource::Type::Local;
}
@ -62,9 +64,9 @@ inline std::string toString(const ImageSource &value) {
return "{uri: " + value.uri + "}";
}
inline void fromDynamic(const folly::dynamic &value, ImageResizeMode &result) {
assert(value.isString());
auto stringValue = value.asString();
inline void fromRawValue(const RawValue &value, ImageResizeMode &result) {
assert(value.hasType<std::string>());
auto stringValue = (std::string)value;
if (stringValue == "cover") {
result = ImageResizeMode::Cover;
return;

View File

@ -13,10 +13,10 @@
namespace facebook {
namespace react {
inline void fromDynamic(
const folly::dynamic &value,
inline void fromRawValue(
const RawValue &value,
ScrollViewSnapToAlignment &result) {
auto string = value.asString();
auto string = (std::string)value;
if (string == "start") {
result = ScrollViewSnapToAlignment::Start;
return;
@ -32,10 +32,10 @@ inline void fromDynamic(
abort();
}
inline void fromDynamic(
const folly::dynamic &value,
inline void fromRawValue(
const RawValue &value,
ScrollViewIndicatorStyle &result) {
auto string = value.asString();
auto string = (std::string)value;
if (string == "default") {
result = ScrollViewIndicatorStyle::Default;
return;
@ -51,10 +51,10 @@ inline void fromDynamic(
abort();
}
inline void fromDynamic(
const folly::dynamic &value,
inline void fromRawValue(
const RawValue &value,
ScrollViewKeyboardDismissMode &result) {
auto string = value.asString();
auto string = (std::string)value;
if (string == "none") {
result = ScrollViewKeyboardDismissMode::None;
return;

View File

@ -81,20 +81,18 @@ inline void fromString(const std::string &string, AccessibilityTraits &result) {
abort();
}
inline void fromDynamic(
const folly::dynamic &value,
AccessibilityTraits &result) {
if (value.isString()) {
fromString(value.asString(), result);
inline void fromRawValue(const RawValue &value, AccessibilityTraits &result) {
if (value.hasType<std::string>()) {
fromString((std::string)value, result);
return;
}
if (value.isArray()) {
if (value.hasType<std::vector<std::string>>()) {
result = {};
for (auto &item : value) {
auto string = item.asString();
auto items = (std::vector<std::string>)value;
for (auto &item : items) {
AccessibilityTraits itemAccessibilityTraits;
fromString(value.asString(), itemAccessibilityTraits);
fromString(item, itemAccessibilityTraits);
result = result | itemAccessibilityTraits;
}
}

View File

@ -126,9 +126,9 @@ inline YGDirection yogaDirectionFromLayoutDirection(LayoutDirection direction) {
}
}
inline void fromDynamic(const folly::dynamic &value, YGDirection &result) {
assert(value.isString());
auto stringValue = value.asString();
inline void fromRawValue(const RawValue &value, YGDirection &result) {
assert(value.hasType<std::string>());
auto stringValue = (std::string)value;
if (stringValue == "inherit") {
result = YGDirectionInherit;
return;
@ -144,9 +144,9 @@ inline void fromDynamic(const folly::dynamic &value, YGDirection &result) {
abort();
}
inline void fromDynamic(const folly::dynamic &value, YGFlexDirection &result) {
assert(value.isString());
auto stringValue = value.asString();
inline void fromRawValue(const RawValue &value, YGFlexDirection &result) {
assert(value.hasType<std::string>());
auto stringValue = (std::string)value;
if (stringValue == "column") {
result = YGFlexDirectionColumn;
return;
@ -166,9 +166,9 @@ inline void fromDynamic(const folly::dynamic &value, YGFlexDirection &result) {
abort();
}
inline void fromDynamic(const folly::dynamic &value, YGJustify &result) {
assert(value.isString());
auto stringValue = value.asString();
inline void fromRawValue(const RawValue &value, YGJustify &result) {
assert(value.hasType<std::string>());
auto stringValue = (std::string)value;
if (stringValue == "flex-start") {
result = YGJustifyFlexStart;
return;
@ -196,9 +196,9 @@ inline void fromDynamic(const folly::dynamic &value, YGJustify &result) {
abort();
}
inline void fromDynamic(const folly::dynamic &value, YGAlign &result) {
assert(value.isString());
auto stringValue = value.asString();
inline void fromRawValue(const RawValue &value, YGAlign &result) {
assert(value.hasType<std::string>());
auto stringValue = (std::string)value;
if (stringValue == "auto") {
result = YGAlignAuto;
return;
@ -234,9 +234,9 @@ inline void fromDynamic(const folly::dynamic &value, YGAlign &result) {
abort();
}
inline void fromDynamic(const folly::dynamic &value, YGPositionType &result) {
assert(value.isString());
auto stringValue = value.asString();
inline void fromRawValue(const RawValue &value, YGPositionType &result) {
assert(value.hasType<std::string>());
auto stringValue = (std::string)value;
if (stringValue == "relative") {
result = YGPositionTypeRelative;
return;
@ -248,9 +248,9 @@ inline void fromDynamic(const folly::dynamic &value, YGPositionType &result) {
abort();
}
inline void fromDynamic(const folly::dynamic &value, YGWrap &result) {
assert(value.isString());
auto stringValue = value.asString();
inline void fromRawValue(const RawValue &value, YGWrap &result) {
assert(value.hasType<std::string>());
auto stringValue = (std::string)value;
if (stringValue == "no-wrap") {
result = YGWrapNoWrap;
return;
@ -266,9 +266,9 @@ inline void fromDynamic(const folly::dynamic &value, YGWrap &result) {
abort();
}
inline void fromDynamic(const folly::dynamic &value, YGOverflow &result) {
assert(value.isString());
auto stringValue = value.asString();
inline void fromRawValue(const RawValue &value, YGOverflow &result) {
assert(value.hasType<std::string>());
auto stringValue = (std::string)value;
if (stringValue == "visible") {
result = YGOverflowVisible;
return;
@ -284,9 +284,9 @@ inline void fromDynamic(const folly::dynamic &value, YGOverflow &result) {
abort();
}
inline void fromDynamic(const folly::dynamic &value, YGDisplay &result) {
assert(value.isString());
auto stringValue = value.asString();
inline void fromRawValue(const RawValue &value, YGDisplay &result) {
assert(value.hasType<std::string>());
auto stringValue = (std::string)value;
if (stringValue == "flex") {
result = YGDisplayFlex;
return;
@ -298,14 +298,14 @@ inline void fromDynamic(const folly::dynamic &value, YGDisplay &result) {
abort();
}
inline void fromDynamic(
const folly::dynamic &value,
inline void fromRawValue(
const RawValue &value,
decltype(YGStyle{}.margin[0]) /* type is subject to change */ &result) {
if (value.isNumber()) {
result = yogaStyleValueFromFloat(value.asDouble());
if (value.hasType<Float>()) {
result = yogaStyleValueFromFloat((Float)value);
return;
} else if (value.isString()) {
const auto stringValue = value.asString();
} else if (value.hasType<std::string>()) {
const auto stringValue = (std::string)value;
if (stringValue == "auto") {
result = YGValueUndefined;
return;
@ -324,12 +324,12 @@ inline void fromDynamic(
result = YGValueUndefined;
}
inline void fromDynamic(const folly::dynamic &value, YGFloatOptional &result) {
if (value.isNumber()) {
result = YGFloatOptional(value.asDouble());
inline void fromRawValue(const RawValue &value, YGFloatOptional &result) {
if (value.hasType<float>()) {
result = YGFloatOptional((float)value);
return;
} else if (value.isString()) {
const auto stringValue = value.asString();
} else if (value.hasType<std::string>()) {
const auto stringValue = (std::string)value;
if (stringValue == "auto") {
result = YGFloatOptional();
return;
@ -338,76 +338,74 @@ inline void fromDynamic(const folly::dynamic &value, YGFloatOptional &result) {
abort();
}
inline void fromDynamic(const folly::dynamic &value, Transform &result) {
assert(value.isArray());
inline void fromRawValue(const RawValue &value, Transform &result) {
assert(value.hasType<std::vector<RawValue>>());
auto transformMatrix = Transform{};
for (const auto &tranformConfiguration : value) {
assert(tranformConfiguration.isObject());
auto pair = *tranformConfiguration.items().begin();
const auto &operation = pair.first.asString();
const auto &parameters = pair.second;
auto configurations = (std::vector<RawValue>)value;
for (const auto &configuration : configurations) {
auto configurationPair =
(std::unordered_map<std::string, RawValue>)configuration;
auto pair = configurationPair.begin();
auto operation = pair->first;
auto &parameters = pair->second;
if (operation == "matrix") {
assert(parameters.isArray());
assert(parameters.size() == transformMatrix.matrix.size());
assert(parameters.hasType<std::vector<Float>>());
auto numbers = (std::vector<Float>)parameters;
assert(numbers.size() == transformMatrix.matrix.size());
auto i = 0;
for (auto item : parameters) {
transformMatrix.matrix[i++] = (Float)item.asDouble();
for (auto number : numbers) {
transformMatrix.matrix[i++] = number;
}
} else if (operation == "perspective") {
transformMatrix = transformMatrix *
Transform::Perspective((Float)parameters.asDouble());
} else if (operation == "rotateX") {
transformMatrix = transformMatrix *
Transform::Rotate((Float)parameters.asDouble(), 0, 0);
} else if (operation == "rotateY") {
transformMatrix = transformMatrix *
Transform::Rotate(0, (Float)parameters.asDouble(), 0);
} else if (operation == "rotateZ") {
transformMatrix = transformMatrix *
Transform::Rotate(0, 0, (Float)parameters.asDouble());
} else if (operation == "scale") {
transformMatrix = transformMatrix *
Transform::Scale((Float)parameters.asDouble(),
(Float)parameters.asDouble(),
(Float)parameters.asDouble());
} else if (operation == "scaleX") {
transformMatrix = transformMatrix *
Transform::Scale((Float)parameters.asDouble(), 0, 0);
} else if (operation == "scaleY") {
transformMatrix = transformMatrix *
Transform::Scale(0, (Float)parameters.asDouble(), 0);
} else if (operation == "scaleZ") {
transformMatrix = transformMatrix *
Transform::Scale(0, 0, (Float)parameters.asDouble());
} else if (operation == "translate") {
transformMatrix =
transformMatrix *
Transform::Translate(
parameters[0].asDouble(), parameters[1].asDouble(), 0);
transformMatrix * Transform::Perspective((Float)parameters);
} else if (operation == "rotateX") {
transformMatrix =
transformMatrix * Transform::Rotate((Float)parameters, 0, 0);
} else if (operation == "rotateY") {
transformMatrix =
transformMatrix * Transform::Rotate(0, (Float)parameters, 0);
} else if (operation == "rotateZ") {
transformMatrix =
transformMatrix * Transform::Rotate(0, 0, (Float)parameters);
} else if (operation == "scale") {
auto number = (Float)parameters;
transformMatrix =
transformMatrix * Transform::Scale(number, number, number);
} else if (operation == "scaleX") {
transformMatrix =
transformMatrix * Transform::Scale((Float)parameters, 0, 0);
} else if (operation == "scaleY") {
transformMatrix =
transformMatrix * Transform::Scale(0, (Float)parameters, 0);
} else if (operation == "scaleZ") {
transformMatrix =
transformMatrix * Transform::Scale(0, 0, (Float)parameters);
} else if (operation == "translate") {
auto numbers = (std::vector<Float>)parameters;
transformMatrix = transformMatrix *
Transform::Translate(numbers.at(0), numbers.at(1), 0);
} else if (operation == "translateX") {
transformMatrix =
transformMatrix * Transform::Translate(parameters.asDouble(), 0, 0);
transformMatrix * Transform::Translate((Float)parameters, 0, 0);
} else if (operation == "translateY") {
transformMatrix =
transformMatrix * Transform::Translate(0, parameters.asDouble(), 0);
transformMatrix * Transform::Translate(0, (Float)parameters, 0);
} else if (operation == "skewX") {
transformMatrix =
transformMatrix * Transform::Skew(parameters.asDouble(), 0);
transformMatrix = transformMatrix * Transform::Skew((Float)parameters, 0);
} else if (operation == "skewY") {
transformMatrix =
transformMatrix * Transform::Skew(0, parameters.asDouble());
transformMatrix = transformMatrix * Transform::Skew(0, (Float)parameters);
}
}
result = transformMatrix;
}
inline void fromDynamic(
const folly::dynamic &value,
PointerEventsMode &result) {
assert(value.isString());
auto stringValue = value.asString();
inline void fromRawValue(const RawValue &value, PointerEventsMode &result) {
assert(value.hasType<std::string>());
auto stringValue = (std::string)value;
if (stringValue == "auto") {
result = PointerEventsMode::Auto;
return;
@ -427,9 +425,9 @@ inline void fromDynamic(
abort();
}
inline void fromDynamic(const folly::dynamic &value, BorderStyle &result) {
assert(value.isString());
auto stringValue = value.asString();
inline void fromRawValue(const RawValue &value, BorderStyle &result) {
assert(value.hasType<std::string>());
auto stringValue = (std::string)value;
if (stringValue == "solid") {
result = BorderStyle::Solid;
return;

View File

@ -0,0 +1,8 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RawProps.h"

View File

@ -0,0 +1,97 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <folly/dynamic.h>
#include <jsi/JSIDynamic.h>
#include <jsi/jsi.h>
#include <react/core/RawValue.h>
namespace facebook {
namespace react {
/*
* `RawProps` represents an untyped map of props comes from JavaScript side.
* `RawProps` stores JSI (or `folly::dynamic`) primitives inside and abstract
* them as `RawValue` objects.
* `RawProps` is NOT a thread-safe type nor long-living type.
* The caller must not store values of this type.
* The class is practically a wrapper around a `jsi::Value and `jsi::Runtime`
* pair (or folly::dynamic) preventing direct access to it and inefficient
* misuse. Not copyable, not moveable.
*/
class RawProps {
public:
/*
* Creates an object with given `runtime` and `value`.
*/
RawProps(jsi::Runtime &runtime, const jsi::Value &value) noexcept
: RawProps(
value.isNull() ? folly::dynamic::object()
: jsi::dynamicFromValue(runtime, value)) {}
/*
* Creates an object with given `folly::dynamic` object.
* Deprecated.
* We need this temporary, only because we have a callsite that does not have
* a `jsi::Runtime` behind the data.
*/
RawProps(const folly::dynamic &dynamic) noexcept
:
#ifdef ANDROID
dynamic_(dynamic),
#endif
map_((std::unordered_map<std::string, RawValue>)RawValue(dynamic)) {
}
/*
* Not moveable.
*/
RawProps(RawProps &&other) noexcept = delete;
RawProps &operator=(RawProps &&other) noexcept = delete;
/*
* Not copyable.
*/
RawProps(const RawProps &other) noexcept = delete;
RawProps &operator=(const RawProps &other) noexcept = delete;
#ifdef ANDROID
/*
* Deprecated. Do not use.
* The support for explicit conversion to `folly::dynamic` is deprecated and
* will be removed as soon Android implementation does not need it.
*/
explicit operator folly::dynamic() const noexcept {
return dynamic_;
}
#endif
/*
* Returns a const unowning pointer to `RawValue` of a prop with a given name.
* Returns `nullptr`, it a prop with the given name does not exist.
*/
const RawValue *at(const std::string &name) const noexcept {
auto iterator = map_.find(name);
if (iterator == map_.end()) {
return nullptr;
}
return &iterator->second;
}
private:
#ifdef ANDROID
const folly::dynamic dynamic_;
#endif
const std::unordered_map<std::string, RawValue> map_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,8 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RawValue.h"

View File

@ -0,0 +1,266 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <folly/dynamic.h>
#include <jsi/JSIDynamic.h>
#include <jsi/jsi.h>
namespace facebook {
namespace react {
class RawProps;
/*
* `RawValue` abstracts some arbitrary complex data structure similar to JSON.
* `RawValue` supports explicit conversion to: `bool`, `int`, `int64_t`,
* `float`, `double`, `string`, and `vector` & `map` of those types and itself.
*
* The main intention of the class is to abstract React props parsing infra from
* JSI, to enable support for any non-JSI-based data sources. The particular
* implementation of the interface is a very slim abstraction around
* `folly::dynamic` though.
* In the near future, this class will hold a `jsi::Runtime` and `jsi::Value`
* pair instead of `folly::dynamic`.
*
* How `RawValue` is different from `JSI::Value`:
* * `RawValue` provides much more scoped API without any references to
* JavaScript specifics.
* * The API is much more C++-idiomatic and easy to use from C++ code.
* * The API prevents access to JSI/JavaScript internals from prop-parsing
* code.
* * The `RawValue` is not copyable nor thread-safe, which prevent
* misuse and accidental performance problems.
*
* How `RawValue` is different from `folly::dynamic`:
* * `RawValue` is a lazy data structure, it does not copy all content inside,
* it provides efficient SAX-like access to the data.
* * `RawValue` has more static and C++-idiomatic API.
* * The `RawValue` is not copyable nor thread-safe, which prevent
* misuse and accidental performance problems.
*/
class RawValue {
public:
/*
* Constructors.
*/
RawValue() noexcept : dynamic_(nullptr){};
RawValue(RawValue &&other) noexcept : dynamic_(std::move(other.dynamic_)) {}
RawValue &operator=(RawValue &&other) noexcept {
if (this != &other) {
dynamic_ = std::move(other.dynamic_);
}
return *this;
}
private:
friend RawProps;
/*
* Arbitrary constructors are private only for RawProps and internal usage.
*/
RawValue(const folly::dynamic &dynamic) noexcept : dynamic_(dynamic){};
RawValue(folly::dynamic &&dynamic) noexcept : dynamic_(std::move(dynamic)){};
/*
* Copy constructor and copy assignment operator are private and only for
* internal use. Basically, it's implementation details. Other particular
* implementations of the `RawValue` interface may not have them.
*/
RawValue(RawValue const &other) noexcept : dynamic_(other.dynamic_) {}
RawValue &operator=(const RawValue &other) noexcept {
if (this != &other) {
dynamic_ = other.dynamic_;
}
return *this;
}
public:
/*
* Casts the value to a specified type.
*/
template <typename T>
explicit operator T() const noexcept {
return castValue(dynamic_, (T *)nullptr);
}
inline explicit operator folly::dynamic() const {
return dynamic_;
}
/*
* Checks if the stored value has specified type.
*/
template <typename T>
bool hasType() const noexcept {
return checkValueType(dynamic_, (T *)nullptr);
};
/*
* Checks if the stored value is *not* `null`.
*/
bool hasValue() const noexcept {
return !dynamic_.isNull();
}
private:
folly::dynamic dynamic_;
static bool checkValueType(
const folly::dynamic &dynamic,
RawValue *type) noexcept {
return true;
}
static bool checkValueType(
const folly::dynamic &dynamic,
bool *type) noexcept {
return dynamic.isBool();
}
static bool checkValueType(
const folly::dynamic &dynamic,
int *type) noexcept {
return dynamic.isNumber();
}
static bool checkValueType(
const folly::dynamic &dynamic,
int64_t *type) noexcept {
return dynamic.isNumber();
}
static bool checkValueType(
const folly::dynamic &dynamic,
float *type) noexcept {
return dynamic.isNumber();
}
static bool checkValueType(
const folly::dynamic &dynamic,
double *type) noexcept {
return dynamic.isNumber();
}
static bool checkValueType(
const folly::dynamic &dynamic,
std::string *type) noexcept {
return dynamic.isString();
}
template <typename T>
static bool checkValueType(
const folly::dynamic &dynamic,
std::vector<T> *type) noexcept {
if (!dynamic.isArray()) {
return false;
}
for (const auto &item : dynamic) {
if (!checkValueType(item, (T *)nullptr)) {
return false;
}
// Note: We test only one element.
break;
}
return true;
}
template <typename T>
static bool checkValueType(
const folly::dynamic &dynamic,
std::unordered_map<std::string, T> *type) noexcept {
if (!dynamic.isObject()) {
return false;
}
for (const auto &item : dynamic.items()) {
assert(item.first.isString());
if (!checkValueType(item.second, (T *)nullptr)) {
return false;
}
// Note: We test only one element.
break;
}
return true;
}
// Casts
static RawValue castValue(
const folly::dynamic &dynamic,
RawValue *type) noexcept {
return RawValue(dynamic);
}
static bool castValue(const folly::dynamic &dynamic, bool *type) noexcept {
return dynamic.getBool();
}
static int castValue(const folly::dynamic &dynamic, int *type) noexcept {
return dynamic.asInt();
}
static int64_t castValue(
const folly::dynamic &dynamic,
int64_t *type) noexcept {
return dynamic.asInt();
}
static float castValue(const folly::dynamic &dynamic, float *type) noexcept {
return dynamic.asDouble();
}
static double castValue(
const folly::dynamic &dynamic,
double *type) noexcept {
return dynamic.asDouble();
}
static std::string castValue(
const folly::dynamic &dynamic,
std::string *type) noexcept {
return dynamic.getString();
}
template <typename T>
static std::vector<T> castValue(
const folly::dynamic &dynamic,
std::vector<T> *type) noexcept {
assert(dynamic.isArray());
auto result = std::vector<T>{};
result.reserve(dynamic.size());
for (const auto &item : dynamic) {
result.push_back(castValue(item, (T *)nullptr));
}
return result;
}
template <typename T>
static std::unordered_map<std::string, T> castValue(
const folly::dynamic &dynamic,
std::unordered_map<std::string, T> *type) noexcept {
assert(dynamic.isObject());
auto result = std::unordered_map<std::string, T>{};
for (const auto &item : dynamic.items()) {
assert(item.first.isString());
result[item.first.getString()] = castValue(item.second, (T *)nullptr);
}
return result;
}
};
} // namespace react
} // namespace facebook

View File

@ -7,12 +7,13 @@
#pragma once
#include <folly/dynamic.h>
#include <react/core/RawProps.h>
#include <react/core/RawValue.h>
#include <memory>
#include <string>
#include <unordered_map>
#include <folly/dynamic.h>
namespace facebook {
namespace react {
@ -28,13 +29,6 @@ using InstanceHandle = struct InstanceHandleDummyStruct {
*/
using SurfaceId = int32_t;
/*
* `RawProps` represents untyped map with props comes from JavaScript side.
*/
// TODO(T26954420): Use iterator as underlying type for RawProps.
using RawProps = std::unordered_map<std::string, folly::dynamic>;
using SharedRawProps = std::shared_ptr<const RawProps>;
/*
* Universal component handle which allows to refer to `ComponentDescriptor`s
* in maps efficiently.

View File

@ -9,6 +9,7 @@
#include <folly/Optional.h>
#include <folly/dynamic.h>
#include <react/core/RawProps.h>
#include <react/graphics/Color.h>
#include <react/graphics/Geometry.h>
#include <react/graphics/conversions.h>
@ -16,90 +17,78 @@
namespace facebook {
namespace react {
inline void fromDynamic(const folly::dynamic &value, bool &result) {
result = value.getBool();
}
inline void fromDynamic(const folly::dynamic &value, int &result) {
// All numbers from JS are treated as double, and JS cannot represent int64 in
// practice. So this always converts the value to int64 instead.
result = value.asInt();
}
inline void fromDynamic(const folly::dynamic &value, float &result) {
result = (float)value.asDouble();
}
inline void fromDynamic(const folly::dynamic &value, double &result) {
result = value.asDouble();
}
inline void fromDynamic(const folly::dynamic &value, std::string &result) {
result = value.getString();
}
inline void fromDynamic(const folly::dynamic &value, folly::dynamic &result) {
result = value;
template <typename T>
void fromRawValue(const RawValue &rawValue, T &result) {
result = (T)rawValue;
}
template <typename T>
inline void fromDynamic(const folly::dynamic &value, std::vector<T> &result) {
if (!value.isArray()) {
T itemResult;
fromDynamic(value, itemResult);
result = {itemResult};
void fromRawValue(const RawValue &rawValue, std::vector<T> &result) {
if (rawValue.hasType<std::vector<RawValue>>()) {
auto items = (std::vector<RawValue>)rawValue;
auto length = items.size();
result.clear();
result.reserve(length);
for (int i = 0; i < length; i++) {
T itemResult;
fromRawValue(items.at(i), itemResult);
result.push_back(itemResult);
}
return;
}
// The case where `value` is not an array.
result.clear();
result.reserve(1);
T itemResult;
for (auto &itemValue : value) {
fromDynamic(itemValue, itemResult);
result.push_back(itemResult);
}
fromRawValue(rawValue, itemResult);
result.push_back(itemResult);
}
template <typename T>
inline T convertRawProp(
T convertRawProp(
const RawProps &rawProps,
const std::string &name,
const T &sourceValue,
const T &defaultValue = T()) {
const auto &iterator = rawProps.find(name);
if (iterator == rawProps.end()) {
const auto rawValue = rawProps.at(name);
if (!rawValue) {
return sourceValue;
}
const auto &value = iterator->second;
// Special case: `null` always means `the prop was removed, use default
// value`.
if (value.isNull()) {
if (!rawValue->hasValue()) {
return defaultValue;
}
T result;
fromDynamic(value, result);
fromRawValue(*rawValue, result);
return result;
}
template <typename T>
inline static folly::Optional<T> convertRawProp(
static folly::Optional<T> convertRawProp(
const RawProps &rawProps,
const std::string &name,
const folly::Optional<T> &sourceValue,
const folly::Optional<T> &defaultValue = {}) {
const auto &iterator = rawProps.find(name);
if (iterator == rawProps.end()) {
const auto rawValue = rawProps.at(name);
if (!rawValue) {
return sourceValue;
}
const auto &value = iterator->second;
// Special case: `null` always means `the prop was removed, use default
// value`.
if (value.isNull()) {
if (!rawValue->hasValue()) {
return defaultValue;
}
T result;
fromDynamic(value, result);
return result;
fromRawValue(*rawValue, result);
return folly::Optional<T>{result};
}
} // namespace react

View File

@ -17,7 +17,7 @@ Props::Props(const Props &sourceProps, const RawProps &rawProps)
: nativeId(convertRawProp(rawProps, "nativeID", sourceProps.nativeId))
#ifdef ANDROID
,
rawProps(rawProps)
rawProps((folly::dynamic)rawProps)
#endif
{};

View File

@ -32,7 +32,7 @@ class Props : public virtual Sealable, public virtual DebugStringConvertible {
const std::string nativeId;
#ifdef ANDROID
const RawProps rawProps;
const folly::dynamic rawProps = folly::dynamic::object();
#endif
};

View File

@ -20,8 +20,7 @@ TEST(ComponentDescriptorTest, createShadowNode) {
descriptor->getComponentName().c_str(), TestShadowNode::Name().c_str());
ASSERT_STREQ(descriptor->getComponentName().c_str(), "Test");
RawProps raw;
raw["nativeID"] = "abc";
const auto &raw = RawProps(folly::dynamic::object("nativeID", "abc"));
SharedProps props = descriptor->cloneProps(nullptr, raw);
SharedShadowNode node = descriptor->createShadowNode(
ShadowNodeFragment{.tag = 9,
@ -42,8 +41,7 @@ TEST(ComponentDescriptorTest, cloneShadowNode) {
SharedComponentDescriptor descriptor =
std::make_shared<TestComponentDescriptor>(nullptr);
RawProps raw;
raw["nativeID"] = "abc";
const auto &raw = RawProps(folly::dynamic::object("nativeID", "abc"));
SharedProps props = descriptor->cloneProps(nullptr, raw);
SharedShadowNode node = descriptor->createShadowNode(
ShadowNodeFragment{.tag = 9,
@ -62,8 +60,7 @@ TEST(ComponentDescriptorTest, appendChild) {
SharedComponentDescriptor descriptor =
std::make_shared<TestComponentDescriptor>(nullptr);
RawProps raw;
raw["nativeID"] = "abc";
const auto &raw = RawProps(folly::dynamic::object("nativeID", "abc"));
SharedProps props = descriptor->cloneProps(nullptr, raw);
SharedShadowNode node1 = descriptor->createShadowNode(
ShadowNodeFragment{.tag = 1,

View File

@ -16,8 +16,7 @@
using namespace facebook::react;
TEST(ShadowNodeTest, handleProps) {
RawProps raw;
raw["nativeID"] = "abc";
const auto &raw = RawProps(folly::dynamic::object("nativeID", "abc"));
auto props = std::make_shared<Props>(Props(), raw);

View File

@ -13,6 +13,7 @@
#include <react/core/ConcreteComponentDescriptor.h>
#include <react/core/ConcreteShadowNode.h>
#include <react/core/LocalData.h>
#include <react/core/RawProps.h>
#include <react/core/ShadowNode.h>
using namespace facebook::react;
@ -41,7 +42,8 @@ static const char TestComponentName[] = "Test";
class TestProps : public Props {
public:
using Props::Props;
TestProps() : Props(Props(), {{"nativeID", "testNativeID"}}) {}
TestProps()
: Props(Props(), RawProps(folly::dynamic::object("nativeID", "testNativeID"))) {}
};
using SharedTestProps = std::shared_ptr<const TestProps>;

View File

@ -8,6 +8,7 @@
#pragma once
#include <folly/dynamic.h>
#include <react/core/RawProps.h>
#include <react/graphics/Color.h>
#include <react/graphics/Geometry.h>
@ -16,30 +17,30 @@ namespace react {
#pragma mark - Color
inline void fromDynamic(const folly::dynamic &value, SharedColor &result) {
inline void fromRawValue(const RawValue &value, SharedColor &result) {
float red;
float green;
float blue;
float alpha;
if (value.isNumber()) {
auto argb = value.asInt();
if (value.hasType<int>()) {
auto argb = (int64_t)value;
auto ratio = 256.f;
alpha = ((argb >> 24) & 0xFF) / ratio;
red = ((argb >> 16) & 0xFF) / ratio;
green = ((argb >> 8) & 0xFF) / ratio;
blue = (argb & 0xFF) / ratio;
} else if (value.isArray()) {
auto size = value.size();
assert(size == 3 || size == 4);
red = value[0].asDouble();
green = value[1].asDouble();
blue = value[2].asDouble();
alpha = size == 4 ? value[3].asDouble() : 1.0;
} else if (value.hasType<std::vector<float>>()) {
auto items = (std::vector<float>)value;
auto length = items.size();
assert(length == 3 || length == 4);
red = items.at(0);
green = items.at(1);
blue = items.at(2);
alpha = length == 4 ? items.at(3) : 1.0;
} else {
abort();
}
result = colorFromComponents({red, green, blue, alpha});
}
@ -68,74 +69,84 @@ inline std::string toString(const SharedColor &value) {
#pragma mark - Geometry
inline void fromDynamic(const folly::dynamic &value, Point &result) {
if (value.isObject()) {
result = Point{(Float)value["x"].asDouble(), (Float)value["y"].asDouble()};
inline void fromRawValue(const RawValue &value, Point &result) {
if (value.hasType<std::unordered_map<std::string, Float>>()) {
auto map = (std::unordered_map<std::string, Float>)value;
result = {map.at("x"), map.at("y")};
return;
}
if (value.isArray()) {
result = Point{(Float)value[0].asDouble(), (Float)value[1].asDouble()};
if (value.hasType<std::vector<Float>>()) {
auto array = (std::vector<Float>)value;
assert(array.size() == 2);
result = {array.at(0), array.at(1)};
return;
}
abort();
}
inline void fromDynamic(const folly::dynamic &value, Size &result) {
if (value.isObject()) {
result = Size{(Float)value["width"].asDouble(),
(Float)value["height"].asDouble()};
inline void fromRawValue(const RawValue &value, Size &result) {
if (value.hasType<std::unordered_map<std::string, Float>>()) {
auto map = (std::unordered_map<std::string, Float>)value;
result = {map.at("width"), map.at("height")};
return;
}
if (value.isArray()) {
result = Size{(Float)value[0].asDouble(), (Float)value[1].asDouble()};
if (value.hasType<std::vector<Float>>()) {
auto array = (std::vector<Float>)value;
assert(array.size() == 2);
result = {array.at(0), array.at(1)};
return;
}
abort();
}
inline void fromDynamic(const folly::dynamic &value, EdgeInsets &result) {
if (value.isNumber()) {
const Float number = value.asDouble();
result = EdgeInsets{number, number, number, number};
inline void fromRawValue(const RawValue &value, EdgeInsets &result) {
if (value.hasType<Float>()) {
auto number = (Float)value;
result = {number, number, number, number};
}
if (value.hasType<std::unordered_map<std::string, Float>>()) {
auto map = (std::unordered_map<std::string, Float>)value;
result = {map.at("top"), map.at("left"), map.at("bottom"), map.at("right")};
return;
}
if (value.isObject()) {
result = EdgeInsets{(Float)value["top"].asDouble(),
(Float)value["left"].asDouble(),
(Float)value["bottom"].asDouble(),
(Float)value["right"].asDouble()};
return;
}
if (value.isArray()) {
result = EdgeInsets{(Float)value[0].asDouble(),
(Float)value[1].asDouble(),
(Float)value[2].asDouble(),
(Float)value[3].asDouble()};
if (value.hasType<std::vector<Float>>()) {
auto array = (std::vector<Float>)value;
assert(array.size() == 4);
result = {array.at(0), array.at(1), array.at(2), array.at(3)};
return;
}
abort();
}
inline void fromDynamic(const folly::dynamic &value, CornerInsets &result) {
if (value.isNumber()) {
const Float number = value.asDouble();
result = CornerInsets{number, number, number, number};
inline void fromRawValue(const RawValue &value, CornerInsets &result) {
if (value.hasType<Float>()) {
auto number = (Float)value;
result = {number, number, number, number};
}
if (value.hasType<std::unordered_map<std::string, Float>>()) {
auto map = (std::unordered_map<std::string, Float>)value;
result = {map.at("topLeft"),
map.at("topRight"),
map.at("bottomLeft"),
map.at("bottomRight")};
return;
}
if (value.isObject()) {
result = CornerInsets{(Float)value["topLeft"].asDouble(),
(Float)value["topRight"].asDouble(),
(Float)value["bottomLeft"].asDouble(),
(Float)value["bottomRight"].asDouble()};
return;
}
if (value.isArray()) {
result = CornerInsets{(Float)value[0].asDouble(),
(Float)value[1].asDouble(),
(Float)value[2].asDouble(),
(Float)value[3].asDouble()};
if (value.hasType<std::vector<Float>>()) {
auto array = (std::vector<Float>)value;
assert(array.size() == 4);
result = {array.at(0), array.at(1), array.at(2), array.at(3)};
return;
}
abort();
}

View File

@ -112,14 +112,13 @@ SharedShadowNode ComponentDescriptorRegistry::createNode(
const SharedEventTarget &eventTarget) const {
ComponentName componentName = componentNameByReactViewName(viewName);
const SharedComponentDescriptor &componentDescriptor = (*this)[componentName];
RawProps rawProps = rawPropsFromDynamic(props);
SharedShadowNode shadowNode = componentDescriptor->createShadowNode(
{.tag = tag,
.rootTag = rootTag,
.eventEmitter =
componentDescriptor->createEventEmitter(std::move(eventTarget), tag),
.props = componentDescriptor->cloneProps(nullptr, rawProps)});
.props = componentDescriptor->cloneProps(nullptr, RawProps(props))});
return shadowNode;
}

View File

@ -33,17 +33,16 @@ SharedShadowNode UIManager::createNode(
SharedShadowNode UIManager::cloneNode(
const SharedShadowNode &shadowNode,
const SharedShadowNodeSharedList &children,
const folly::Optional<RawProps> &rawProps) const {
const RawProps *rawProps) const {
auto &componentDescriptor =
componentDescriptorRegistry_->at(shadowNode->getComponentHandle());
auto clonedShadowNode = componentDescriptor.cloneShadowNode(
*shadowNode,
{
.props = rawProps.has_value()
? componentDescriptor.cloneProps(
shadowNode->getProps(), rawProps.value())
: ShadowNodeFragment::nullSharedProps(),
.props = rawProps ? componentDescriptor.cloneProps(
shadowNode->getProps(), *rawProps)
: ShadowNodeFragment::nullSharedProps(),
.children = children,
});

View File

@ -42,7 +42,7 @@ class UIManager {
SharedShadowNode cloneNode(
const SharedShadowNode &shadowNode,
const SharedShadowNodeSharedList &children = nullptr,
const folly::Optional<RawProps> &rawProps = {}) const;
const RawProps *rawProps = nullptr) const;
void appendChild(
const SharedShadowNode &parentShadowNode,

View File

@ -120,7 +120,7 @@ jsi::Value UIManagerBinding::get(
tagFromValue(runtime, arguments[0]),
componentNameFromValue(runtime, arguments[1]),
surfaceIdFromValue(runtime, arguments[2]),
rawPropsFromValue(runtime, arguments[3]),
RawProps(runtime, arguments[3]),
eventTargetFromValue(runtime, arguments[4], arguments[0])));
});
}
@ -172,12 +172,13 @@ jsi::Value UIManagerBinding::get(
const jsi::Value &thisValue,
const jsi::Value *arguments,
size_t count) -> jsi::Value {
const auto &rawProps = RawProps(runtime, arguments[1]);
return valueFromShadowNode(
runtime,
uiManager.cloneNode(
shadowNodeFromValue(runtime, arguments[0]),
nullptr,
rawPropsFromValue(runtime, arguments[1])));
&rawProps));
});
}
@ -192,12 +193,13 @@ jsi::Value UIManagerBinding::get(
const jsi::Value &thisValue,
const jsi::Value *arguments,
size_t count) -> jsi::Value {
const auto &rawProps = RawProps(runtime, arguments[1]);
return valueFromShadowNode(
runtime,
uiManager.cloneNode(
shadowNodeFromValue(runtime, arguments[0]),
ShadowNode::emptySharedShadowNodeSharedList(),
rawPropsFromValue(runtime, arguments[1])));
&rawProps));
});
}
@ -319,7 +321,7 @@ jsi::Value UIManagerBinding::get(
size_t count) -> jsi::Value {
uiManager.setNativeProps(
shadowNodeFromValue(runtime, arguments[0]),
rawPropsFromValue(runtime, arguments[1]));
RawProps(runtime, arguments[1]));
return jsi::Value::undefined();
});

View File

@ -13,23 +13,6 @@ namespace react {
using RuntimeExecutor = std::function<void(
std::function<void(facebook::jsi::Runtime &runtime)> &&callback)>;
inline RawProps rawPropsFromDynamic(const folly::dynamic object) noexcept {
RawProps result;
if (object.isNull()) {
return result;
}
assert(object.isObject());
for (const auto &pair : object.items()) {
assert(pair.first.isString());
result[pair.first.asString()] = pair.second;
}
return result;
}
struct EventHandlerWrapper : public EventHandler {
EventHandlerWrapper(jsi::Function eventHandler)
: callback(std::move(eventHandler)) {}
@ -81,13 +64,6 @@ inline static jsi::Value valueFromShadowNodeList(
runtime, std::make_unique<ShadowNodeListWrapper>(shadowNodeList));
}
inline static RawProps rawPropsFromValue(
jsi::Runtime &runtime,
const jsi::Value &value) {
return rawPropsFromDynamic(folly::dynamic{
value.isNull() ? nullptr : jsi::dynamicFromValue(runtime, value)});
}
inline static SharedEventTarget eventTargetFromValue(
jsi::Runtime &runtime,
const jsi::Value &eventTargetValue,

View File

@ -56,8 +56,8 @@ class ::_CLASSNAME_:: final::_EXTEND_CLASSES_:: {
const enumTemplate = `
enum class ::_ENUM_NAME_:: { ::_VALUES_:: };
static inline void fromDynamic(const folly::dynamic &value, ::_ENUM_NAME_:: &result) {
auto string = value.asString();
static inline void fromRawValue(const RawValue &value, ::_ENUM_NAME_:: &result) {
auto string = (std::string)value;
::_FROM_CASES_::
abort();
}

View File

@ -80,8 +80,8 @@ namespace react {
enum class EnumPropsNativeComponentAlignment { Top, Center, Bottom };
static inline void fromDynamic(const folly::dynamic &value, EnumPropsNativeComponentAlignment &result) {
auto string = value.asString();
static inline void fromRawValue(const RawValue &value, EnumPropsNativeComponentAlignment &result) {
auto string = (std::string)value;
if (string == \\"top\\") { result = EnumPropsNativeComponentAlignment::Top; return; }
if (string == \\"center\\") { result = EnumPropsNativeComponentAlignment::Center; return; }
if (string == \\"bottom\\") { result = EnumPropsNativeComponentAlignment::Bottom; return; }