Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "none",
"comment": "Create JSI implementation for IJSValueReader and IJSValueWriter",
"packageName": "react-native-windows",
"email": "zihanc@microsoft.com",
"dependentChangeType": "none",
"date": "2020-05-26T20:47:12.811Z"
}
133 changes: 133 additions & 0 deletions vnext/Microsoft.ReactNative/JsiReader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "pch.h"
#include "JsiReader.h"
#include <crash/verifyElseCrash.h>

namespace winrt::Microsoft::ReactNative {

//===========================================================================
// JsiReader implementation
//===========================================================================

JsiReader::JsiReader(facebook::jsi::Runtime &runtime, const facebook::jsi::Value &root) noexcept
: m_runtime(runtime), m_root(root) {
SetValue(root);
}

JSValueType JsiReader::ValueType() noexcept {
if (m_currentPrimitiveValue) {
if (m_currentPrimitiveValue.value().isString()) {
return JSValueType::String;
} else if (m_currentPrimitiveValue.value().isBool()) {
return JSValueType::Boolean;
} else if (m_currentPrimitiveValue.value().isNumber()) {
double number = m_currentPrimitiveValue.value().getNumber();

// unfortunately JSI doesn't differentiate int and double
// here we test if the double value can be converted to int without data loss
// treat it like an int if we succeeded

if (floor(number) == number) {
return JSValueType::Int64;
} else {
return JSValueType::Double;
}
}
} else if (m_containers.size() > 0) {
return m_containers[m_containers.size() - 1].Type == ContainerType::Object ? JSValueType::Object
: JSValueType::Array;
}
return JSValueType::Null;
}

bool JsiReader::GetNextObjectProperty(hstring &propertyName) noexcept {
if (m_containers.size() == 0) {
return false;
}

auto &top = m_containers[m_containers.size() - 1];
if (top.Type != ContainerType::Object) {
return false;
}

top.Index++;
if (top.Index < static_cast<int>(top.PropertyNames.value().size(m_runtime))) {
auto propertyId =
top.PropertyNames.value().getValueAtIndex(m_runtime, static_cast<size_t>(top.Index)).getString(m_runtime);
propertyName = winrt::to_hstring(propertyId.utf8(m_runtime));
SetValue(top.CurrentObject.value().getProperty(m_runtime, propertyId));
return true;
} else {
m_containers.pop_back();
return false;
}
}

bool JsiReader::GetNextArrayItem() noexcept {
if (m_containers.size() == 0) {
return false;
}

auto &top = m_containers[m_containers.size() - 1];
if (top.Type != ContainerType::Array) {
return false;
}

top.Index++;
if (top.Index < static_cast<int>(top.CurrentArray.value().size(m_runtime))) {
SetValue(top.CurrentArray.value().getValueAtIndex(m_runtime, static_cast<size_t>(top.Index)));
return true;
} else {
m_containers.pop_back();
return false;
}
}

hstring JsiReader::GetString() noexcept {
if (ValueType() != JSValueType::String) {
return {};
}
return winrt::to_hstring(m_currentPrimitiveValue.value().getString(m_runtime).utf8(m_runtime));
}

bool JsiReader::GetBoolean() noexcept {
if (ValueType() != JSValueType::Boolean) {
return false;
}
return m_currentPrimitiveValue.value().getBool();
}

int64_t JsiReader::GetInt64() noexcept {
if (ValueType() != JSValueType::Int64) {
return 0;
}
return static_cast<int64_t>(m_currentPrimitiveValue.value().getNumber());
}

double JsiReader::GetDouble() noexcept {
auto valueType = ValueType();
if (valueType != JSValueType::Int64 && valueType != JSValueType::Double) {
return 0;
}
return m_currentPrimitiveValue.value().getNumber();
}

void JsiReader::SetValue(const facebook::jsi::Value &value) noexcept {
if (value.isObject()) {
auto obj = value.getObject(m_runtime);
if (obj.isArray(m_runtime)) {
m_containers.push_back(obj.getArray(m_runtime));
} else {
m_containers.push_back({m_runtime, std::move(obj)});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about the design, but functions would fall into this case as well. Should we have a check for that?
i.e. use:

  /// \return true iff the Object is callable.  If so, then \c
  /// getFunction will succeed.
  bool isFunction(Runtime& runtime) const {
    return runtime.isFunction(*this);
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case properties of Function will get enumerated. There are still other cases, you are not able to check all system classes. But this is fine, if users use our codegen, they won't be able to do this in TypeScript.

}
m_currentPrimitiveValue = std::nullopt;
} else if (value.isString() || value.isBool() || value.isNumber()) {
m_currentPrimitiveValue = {m_runtime, value};
} else {
m_currentPrimitiveValue = facebook::jsi::Value::null();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we assert that we have exhaused the options?
I.e. add cases for undefined and null. i.e. internally JSI has:

  enum ValueKind {
    UndefinedKind,
    NullKind,
    BooleanKind,
    NumberKind,
    SymbolKind,
    StringKind,
    ObjectKind,
    PointerKind = SymbolKind,
  };

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in DynamicReader, everything that is not recognizable becomes null. Here I use the same logic. For anything that is not an Object, it will be store in m_currentPrimitiveValue. In here everything other than Object/String/Boolean/Number, becomes null.

}
}

} // namespace winrt::Microsoft::ReactNative
61 changes: 61 additions & 0 deletions vnext/Microsoft.ReactNative/JsiReader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#pragma once

#include "jsi/jsi.h"
#include "winrt/Microsoft.ReactNative.h"

namespace winrt::Microsoft::ReactNative {

struct JsiReader : implements<JsiReader, IJSValueReader> {
JsiReader(facebook::jsi::Runtime &runtime, const facebook::jsi::Value &root) noexcept;

public: // IJSValueReader
JSValueType ValueType() noexcept;
bool GetNextObjectProperty(hstring &propertyName) noexcept;
bool GetNextArrayItem() noexcept;
hstring GetString() noexcept;
bool GetBoolean() noexcept;
int64_t GetInt64() noexcept;
double GetDouble() noexcept;

private:
enum class ContainerType {
Object,
Array,
};

struct Container {
ContainerType Type;
std::optional<facebook::jsi::Object> CurrentObject; // valid for object
std::optional<facebook::jsi::Array> PropertyNames; // valid for object
std::optional<facebook::jsi::Array> CurrentArray; // valid for array
int Index = -1;

Container(facebook::jsi::Runtime &runtime, facebook::jsi::Object &&value) noexcept
: Type(ContainerType::Object), CurrentObject(std::make_optional<facebook::jsi::Object>(std::move(value))) {
PropertyNames = CurrentObject.value().getPropertyNames(runtime);
}

Container(facebook::jsi::Array &&value) noexcept
: Type(ContainerType::Array), CurrentArray(std::make_optional<facebook::jsi::Array>(std::move(value))) {}

Container(const Container &) = delete;
Container(Container &&) = default;
};

private:
void SetValue(const facebook::jsi::Value &value) noexcept;

private:
facebook::jsi::Runtime &m_runtime;
const facebook::jsi::Value &m_root;

// when m_currentPrimitiveValue is not null, the current value is a primitive value
// when m_currentPrimitiveValue is null, the current value is the top value of m_nonPrimitiveValues
std::optional<facebook::jsi::Value> m_currentPrimitiveValue;
std::vector<Container> m_containers;
};

} // namespace winrt::Microsoft::ReactNative
134 changes: 134 additions & 0 deletions vnext/Microsoft.ReactNative/JsiWriter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "pch.h"
#include "JsiWriter.h"
#include <crash/verifyElseCrash.h>

namespace winrt::Microsoft::ReactNative {

//===========================================================================
// JsiWriter implementation
//===========================================================================

JsiWriter::JsiWriter(facebook::jsi::Runtime &runtime) noexcept : m_runtime(runtime) {
Push({ContainerState::AcceptValueAndFinish});
}

facebook::jsi::Value JsiWriter::MoveResult() noexcept {
VerifyElseCrash(m_containers.size() == 0);
return std::move(m_result);
}

void JsiWriter::WriteNull() noexcept {
WriteValue(facebook::jsi::Value::null());
}

void JsiWriter::WriteBoolean(bool value) noexcept {
WriteValue({value});
}

void JsiWriter::WriteInt64(int64_t value) noexcept {
WriteValue({static_cast<double>(value)});
}

void JsiWriter::WriteDouble(double value) noexcept {
WriteValue({value});
}

void JsiWriter::WriteString(const winrt::hstring &value) noexcept {
WriteValue({m_runtime, facebook::jsi::String::createFromUtf8(m_runtime, winrt::to_string(value))});
}

void JsiWriter::WriteObjectBegin() noexcept {
// legal to create an object when it is accepting a value
VerifyElseCrash(Top().State != ContainerState::AcceptPropertyName);
Push({ContainerState::AcceptPropertyName, facebook::jsi::Object(m_runtime)});
}

void JsiWriter::WritePropertyName(const winrt::hstring &name) noexcept {
// legal to set a property name only when AcceptPropertyName
auto &top = Top();
VerifyElseCrash(top.State == ContainerState::AcceptPropertyName);
top.State = ContainerState::AcceptPropertyValue;
top.PropertyName = winrt::to_string(name);
}

void JsiWriter::WriteObjectEnd() noexcept {
// legal to finish an object only when AcceptPropertyName
VerifyElseCrash(Top().State == ContainerState::AcceptPropertyName);
WriteValue(Pop().CurrentObject.value());
}

void JsiWriter::WriteArrayBegin() noexcept {
// legal to create an array only when it is accepting a value
VerifyElseCrash(Top().State != ContainerState::AcceptPropertyName);
Push({ContainerState::AcceptArrayElement});
}

void JsiWriter::WriteArrayEnd() noexcept {
// legal to finish an array only when AcceptArrayElement
auto &top = Top();
VerifyElseCrash(top.State == ContainerState::AcceptArrayElement);

facebook::jsi::Array createdArray(m_runtime, top.CurrentArrayElements.size());
for (size_t i = 0; i < top.CurrentArrayElements.size(); i++) {
createdArray.setValueAtIndex(m_runtime, i, std::move(top.CurrentArrayElements.at(i)));
}
Pop();
WriteValue({m_runtime, createdArray});
}

void JsiWriter::WriteValue(facebook::jsi::Value &&value) noexcept {
auto &top = Top();
switch (top.State) {
case ContainerState::AcceptValueAndFinish: {
m_result = std::move(value);
Pop();
VerifyElseCrash(m_containers.size() == 0);
break;
}
case ContainerState::AcceptArrayElement: {
top.CurrentArrayElements.push_back(std::move(value));
break;
}
case ContainerState::AcceptPropertyValue: {
auto &createdObject = top.CurrentObject.value();
createdObject.setProperty(m_runtime, top.PropertyName.c_str(), std::move(value));
top.State = ContainerState::AcceptPropertyName;
top.PropertyName = {};
break;
}
default:
VerifyElseCrash(false);
}
}

JsiWriter::Container &JsiWriter::Top() noexcept {
VerifyElseCrash(m_containers.size() > 0);
return m_containers[m_containers.size() - 1];
}

JsiWriter::Container JsiWriter::Pop() noexcept {
auto top = std::move(Top());
m_containers.pop_back();
return top;
}

void JsiWriter::Push(Container &&container) noexcept {
m_containers.push_back(std::move(container));
}

/*static*/ facebook::jsi::Value JsiWriter::ToJsiValue(
facebook::jsi::Runtime &runtime,
JSValueArgWriter const &argWriter) noexcept {
if (argWriter) {
IJSValueWriter jsiWriter = winrt::make<JsiWriter>(runtime);
argWriter(jsiWriter);
return jsiWriter.as<JsiWriter>()->MoveResult();
}

return {};
}

} // namespace winrt::Microsoft::ReactNative
Loading