From 01182e8758cf1f3331b5439d081583bf23633111 Mon Sep 17 00:00:00 2001 From: "Zihan Chen (MSFT)" Date: Tue, 26 May 2020 13:46:54 -0700 Subject: [PATCH 1/3] Create JSI implementation for IJSValueReader and IJSValueWriter --- vnext/Microsoft.ReactNative/JsiReader.cpp | 134 ++++++++++++++++ vnext/Microsoft.ReactNative/JsiReader.h | 67 ++++++++ vnext/Microsoft.ReactNative/JsiWriter.cpp | 146 ++++++++++++++++++ vnext/Microsoft.ReactNative/JsiWriter.h | 68 ++++++++ .../Microsoft.ReactNative.vcxproj | 12 ++ 5 files changed, 427 insertions(+) create mode 100644 vnext/Microsoft.ReactNative/JsiReader.cpp create mode 100644 vnext/Microsoft.ReactNative/JsiReader.h create mode 100644 vnext/Microsoft.ReactNative/JsiWriter.cpp create mode 100644 vnext/Microsoft.ReactNative/JsiWriter.h diff --git a/vnext/Microsoft.ReactNative/JsiReader.cpp b/vnext/Microsoft.ReactNative/JsiReader.cpp new file mode 100644 index 00000000000..cd92a401aba --- /dev/null +++ b/vnext/Microsoft.ReactNative/JsiReader.cpp @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "pch.h" +#include "JsiReader.h" +#include + +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 && MinSafeInteger <= number && number <= MaxSafeInteger) { + return JSValueType::Int64; + } else { + return JSValueType::Double; + } + } + } else if (m_nonPrimitiveValues.size() > 0) { + return m_nonPrimitiveValues[m_nonPrimitiveValues.size() - 1].Action == ContinuationAction::MoveToNextObjectProperty + ? JSValueType::Object + : JSValueType::Array; + } + return JSValueType::Null; +} + +bool JsiReader::GetNextObjectProperty(hstring &propertyName) noexcept { + if (m_nonPrimitiveValues.size() == 0) { + return false; + } + + auto &top = m_nonPrimitiveValues[m_nonPrimitiveValues.size() - 1]; + if (top.Action != ContinuationAction::MoveToNextObjectProperty) { + return false; + } + + top.Index++; + if (top.Index < static_cast(top.PropertyNames.value().size(m_runtime))) { + auto propertyId = + top.PropertyNames.value().getValueAtIndex(m_runtime, static_cast(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_nonPrimitiveValues.pop_back(); + return false; + } +} + +bool JsiReader::GetNextArrayItem() noexcept { + if (m_nonPrimitiveValues.size() == 0) { + return false; + } + + auto &top = m_nonPrimitiveValues[m_nonPrimitiveValues.size() - 1]; + if (top.Action != ContinuationAction::MoveToNextArrayElement) { + return false; + } + + top.Index++; + if (top.Index < static_cast(top.CurrentArray.value().size(m_runtime))) { + SetValue(top.CurrentArray.value().getValueAtIndex(m_runtime, static_cast(top.Index))); + return true; + } else { + m_nonPrimitiveValues.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(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_nonPrimitiveValues.push_back(obj.getArray(m_runtime)); + } else { + m_nonPrimitiveValues.push_back({m_runtime, std::move(obj)}); + } + m_currentPrimitiveValue = std::nullopt; + } else if (value.isString() || value.isBool() || value.isNumber()) { + m_currentPrimitiveValue = {m_runtime, value}; + } else { + m_currentPrimitiveValue = facebook::jsi::Value::null(); + } +} + +} // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/JsiReader.h b/vnext/Microsoft.ReactNative/JsiReader.h new file mode 100644 index 00000000000..ed064a300ca --- /dev/null +++ b/vnext/Microsoft.ReactNative/JsiReader.h @@ -0,0 +1,67 @@ +// 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(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 ContinuationAction { + MoveToNextObjectProperty, + MoveToNextArrayElement, + }; + + struct Continuation { + ContinuationAction Action; + std::optional CurrentObject; // valid for object + std::optional PropertyNames; // valid for object + std::optional CurrentArray; // valid for array + int Index = -1; + + Continuation(facebook::jsi::Runtime &runtime, facebook::jsi::Object &&value) noexcept + : Action(ContinuationAction::MoveToNextObjectProperty), + CurrentObject(std::make_optional(std::move(value))) { + PropertyNames = CurrentObject.value().getPropertyNames(runtime); + } + + Continuation(facebook::jsi::Array &&value) noexcept + : Action(ContinuationAction::MoveToNextArrayElement), + CurrentArray(std::make_optional(std::move(value))) {} + + Continuation(const Continuation &) = delete; + Continuation(Continuation &&) = default; + }; + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER + static const int64_t MinSafeInteger = -9007199254740991L; + static const int64_t MaxSafeInteger = 9007199254740991L; + + 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 m_currentPrimitiveValue; + std::vector m_nonPrimitiveValues; +}; + +} // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/JsiWriter.cpp b/vnext/Microsoft.ReactNative/JsiWriter.cpp new file mode 100644 index 00000000000..0e36579648b --- /dev/null +++ b/vnext/Microsoft.ReactNative/JsiWriter.cpp @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "pch.h" +#include "JsiWriter.h" +#include + +namespace winrt::Microsoft::ReactNative { + +//=========================================================================== +// JsiWriter implementation +//=========================================================================== + +JsiWriter::JsiWriter(facebook::jsi::Runtime &runtime) noexcept : m_runtime(runtime) { + Push({ContinuationAction::AcceptValueAndFinish}); +} + +facebook::jsi::Value JsiWriter::MoveResult() noexcept { + VerifyElseCrash(m_continuations.size() == 0); + return std::move(m_result); +} + +facebook::jsi::Value JsiWriter::CopyResult() noexcept { + VerifyElseCrash(m_continuations.size() == 0); + return {m_runtime, 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 { + // JavaScript's integer is not int64_t, need to ensure that the value is in range + VerifyElseCrash(MinSafeInteger <= value && value <= MaxSafeInteger); + + // unfortunately JSI only supports int and double for number, choose double here + // make sure that doing type conversion in C++ makes no data loss + double d = static_cast(value); + VerifyElseCrash(floor(d) == d); + WriteValue(d); +} + +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().Action != ContinuationAction::AcceptPropertyName); + Push({ContinuationAction::AcceptPropertyName, {m_runtime, 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.Action == ContinuationAction::AcceptPropertyName); + top.Action = ContinuationAction::AcceptPropertyValue; + top.PropertyName = winrt::to_string(name); +} + +void JsiWriter::WriteObjectEnd() noexcept { + // legal to finish an object only when AcceptPropertyName + VerifyElseCrash(Top().Action == ContinuationAction::AcceptPropertyName); + auto createdObject = std::move(Pop().Values.at(0)); + WriteValue(std::move(createdObject)); +} + +void JsiWriter::WriteArrayBegin() noexcept { + // legal to create an array only when it is accepting a value + VerifyElseCrash(Top().Action != ContinuationAction::AcceptPropertyName); + Push({ContinuationAction::AcceptArrayElement}); +} + +void JsiWriter::WriteArrayEnd() noexcept { + // legal to finish an array only when AcceptArrayElement + auto &top = Top(); + VerifyElseCrash(top.Action == ContinuationAction::AcceptArrayElement); + facebook::jsi::Array createdArray(m_runtime, top.Values.size()); + for (size_t i = 0; i < top.Values.size(); i++) { + createdArray.setValueAtIndex(m_runtime, i, std::move(top.Values.at(i))); + } + Pop(); + WriteValue({m_runtime, createdArray}); +} + +void JsiWriter::WriteValue(facebook::jsi::Value &&value) noexcept { + auto &top = Top(); + switch (top.Action) { + case ContinuationAction::AcceptValueAndFinish: { + m_result = std::move(value); + Pop(); + VerifyElseCrash(m_continuations.size() == 0); + break; + } + case ContinuationAction::AcceptArrayElement: { + top.Values.push_back(std::move(value)); + break; + } + case ContinuationAction::AcceptPropertyValue: { + auto createdObject = top.Values.at(0).getObject(m_runtime); + createdObject.setProperty(m_runtime, top.PropertyName.c_str(), std::move(value)); + top.Action = ContinuationAction::AcceptPropertyName; + top.PropertyName = {}; + break; + } + default: + VerifyElseCrash(false); + } +} + +JsiWriter::Continuation &JsiWriter::Top() noexcept { + VerifyElseCrash(m_continuations.size() > 0); + return m_continuations[m_continuations.size() - 1]; +} + +JsiWriter::Continuation JsiWriter::Pop() noexcept { + auto top = std::move(Top()); + m_continuations.pop_back(); + return top; +} + +void JsiWriter::Push(Continuation &&continuation) noexcept { + m_continuations.push_back(std::move(continuation)); +} + +/*static*/ facebook::jsi::Value JsiWriter::ToJsiValue( + facebook::jsi::Runtime &runtime, + JSValueArgWriter const &argWriter) noexcept { + if (argWriter) { + IJSValueWriter jsiWriter = winrt::make(runtime); + argWriter(jsiWriter); + return jsiWriter.as()->MoveResult(); + } + + return {}; +} + +} // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/JsiWriter.h b/vnext/Microsoft.ReactNative/JsiWriter.h new file mode 100644 index 00000000000..57f99c65f43 --- /dev/null +++ b/vnext/Microsoft.ReactNative/JsiWriter.h @@ -0,0 +1,68 @@ +#pragma once +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "jsi/jsi.h" +#include "winrt/Microsoft.ReactNative.h" + +namespace winrt::Microsoft::ReactNative { + +struct JsiWriter : winrt::implements { + JsiWriter(facebook::jsi::Runtime &runtime) noexcept; + facebook::jsi::Value MoveResult() noexcept; + facebook::jsi::Value CopyResult() noexcept; + + public: // IJSValueWriter + void WriteNull() noexcept; + void WriteBoolean(bool value) noexcept; + void WriteInt64(int64_t value) noexcept; + void WriteDouble(double value) noexcept; + void WriteString(const winrt::hstring &value) noexcept; + void WriteObjectBegin() noexcept; + void WritePropertyName(const winrt::hstring &name) noexcept; + void WriteObjectEnd() noexcept; + void WriteArrayBegin() noexcept; + void WriteArrayEnd() noexcept; + + public: + static facebook::jsi::Value ToJsiValue(facebook::jsi::Runtime &runtime, JSValueArgWriter const &argWriter) noexcept; + + private: + enum class ContinuationAction { + AcceptValueAndFinish, + AcceptArrayElement, + AcceptPropertyName, + AcceptPropertyValue, + }; + + struct Continuation { + ContinuationAction Action; + std::vector Values; + std::string PropertyName; + + Continuation(ContinuationAction action) noexcept : Action(action) {} + Continuation(ContinuationAction action, facebook::jsi::Value &&value) noexcept : Action(action) { + Values.push_back(std::move(value)); + } + + Continuation(const Continuation &) = delete; + Continuation(Continuation &&) = default; + }; + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER + static const int64_t MinSafeInteger = -9007199254740991L; + static const int64_t MaxSafeInteger = 9007199254740991L; + + private: + void WriteValue(facebook::jsi::Value &&value) noexcept; + Continuation &Top() noexcept; + Continuation Pop() noexcept; + void Push(Continuation &&continuation) noexcept; + + private: + facebook::jsi::Runtime &m_runtime; + facebook::jsi::Value m_result; + std::vector m_continuations; +}; + +} // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index 5053446b741..515f3c9fbcc 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj @@ -312,6 +312,12 @@ IReactNotificationService.idl Code + + IJSValueReader.idl + + + IJSValueWriter.idl + @@ -487,6 +493,12 @@ IReactNotificationService.idl Code + + IJSValueReader.idl + + + IJSValueWriter.idl + From 1576bdd2c6a5eda54d8c5f150e49b14ac0c162e5 Mon Sep 17 00:00:00 2001 From: "Zihan Chen (MSFT)" Date: Tue, 26 May 2020 13:47:13 -0700 Subject: [PATCH 2/3] Change files --- ...t-native-windows-2020-05-26-13-47-12-pull_request.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 change/react-native-windows-2020-05-26-13-47-12-pull_request.json diff --git a/change/react-native-windows-2020-05-26-13-47-12-pull_request.json b/change/react-native-windows-2020-05-26-13-47-12-pull_request.json new file mode 100644 index 00000000000..4ee64488681 --- /dev/null +++ b/change/react-native-windows-2020-05-26-13-47-12-pull_request.json @@ -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" +} From 4c31c0b2b4b6c9e53dd3d24dcc213324451071fe Mon Sep 17 00:00:00 2001 From: "Zihan Chen (MSFT)" Date: Tue, 26 May 2020 16:14:34 -0700 Subject: [PATCH 3/3] Fix code review comments --- vnext/Microsoft.ReactNative/JsiReader.cpp | 29 +++++---- vnext/Microsoft.ReactNative/JsiReader.h | 30 ++++----- vnext/Microsoft.ReactNative/JsiWriter.cpp | 74 ++++++++++------------- vnext/Microsoft.ReactNative/JsiWriter.h | 33 +++++----- 4 files changed, 71 insertions(+), 95 deletions(-) diff --git a/vnext/Microsoft.ReactNative/JsiReader.cpp b/vnext/Microsoft.ReactNative/JsiReader.cpp index cd92a401aba..ea56b90547c 100644 --- a/vnext/Microsoft.ReactNative/JsiReader.cpp +++ b/vnext/Microsoft.ReactNative/JsiReader.cpp @@ -29,27 +29,26 @@ JSValueType JsiReader::ValueType() noexcept { // 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 && MinSafeInteger <= number && number <= MaxSafeInteger) { + if (floor(number) == number) { return JSValueType::Int64; } else { return JSValueType::Double; } } - } else if (m_nonPrimitiveValues.size() > 0) { - return m_nonPrimitiveValues[m_nonPrimitiveValues.size() - 1].Action == ContinuationAction::MoveToNextObjectProperty - ? JSValueType::Object - : JSValueType::Array; + } 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_nonPrimitiveValues.size() == 0) { + if (m_containers.size() == 0) { return false; } - auto &top = m_nonPrimitiveValues[m_nonPrimitiveValues.size() - 1]; - if (top.Action != ContinuationAction::MoveToNextObjectProperty) { + auto &top = m_containers[m_containers.size() - 1]; + if (top.Type != ContainerType::Object) { return false; } @@ -61,18 +60,18 @@ bool JsiReader::GetNextObjectProperty(hstring &propertyName) noexcept { SetValue(top.CurrentObject.value().getProperty(m_runtime, propertyId)); return true; } else { - m_nonPrimitiveValues.pop_back(); + m_containers.pop_back(); return false; } } bool JsiReader::GetNextArrayItem() noexcept { - if (m_nonPrimitiveValues.size() == 0) { + if (m_containers.size() == 0) { return false; } - auto &top = m_nonPrimitiveValues[m_nonPrimitiveValues.size() - 1]; - if (top.Action != ContinuationAction::MoveToNextArrayElement) { + auto &top = m_containers[m_containers.size() - 1]; + if (top.Type != ContainerType::Array) { return false; } @@ -81,7 +80,7 @@ bool JsiReader::GetNextArrayItem() noexcept { SetValue(top.CurrentArray.value().getValueAtIndex(m_runtime, static_cast(top.Index))); return true; } else { - m_nonPrimitiveValues.pop_back(); + m_containers.pop_back(); return false; } } @@ -119,9 +118,9 @@ void JsiReader::SetValue(const facebook::jsi::Value &value) noexcept { if (value.isObject()) { auto obj = value.getObject(m_runtime); if (obj.isArray(m_runtime)) { - m_nonPrimitiveValues.push_back(obj.getArray(m_runtime)); + m_containers.push_back(obj.getArray(m_runtime)); } else { - m_nonPrimitiveValues.push_back({m_runtime, std::move(obj)}); + m_containers.push_back({m_runtime, std::move(obj)}); } m_currentPrimitiveValue = std::nullopt; } else if (value.isString() || value.isBool() || value.isNumber()) { diff --git a/vnext/Microsoft.ReactNative/JsiReader.h b/vnext/Microsoft.ReactNative/JsiReader.h index ed064a300ca..5fd279d980e 100644 --- a/vnext/Microsoft.ReactNative/JsiReader.h +++ b/vnext/Microsoft.ReactNative/JsiReader.h @@ -21,36 +21,30 @@ struct JsiReader : implements { double GetDouble() noexcept; private: - enum class ContinuationAction { - MoveToNextObjectProperty, - MoveToNextArrayElement, + enum class ContainerType { + Object, + Array, }; - struct Continuation { - ContinuationAction Action; + struct Container { + ContainerType Type; std::optional CurrentObject; // valid for object std::optional PropertyNames; // valid for object std::optional CurrentArray; // valid for array int Index = -1; - Continuation(facebook::jsi::Runtime &runtime, facebook::jsi::Object &&value) noexcept - : Action(ContinuationAction::MoveToNextObjectProperty), - CurrentObject(std::make_optional(std::move(value))) { + Container(facebook::jsi::Runtime &runtime, facebook::jsi::Object &&value) noexcept + : Type(ContainerType::Object), CurrentObject(std::make_optional(std::move(value))) { PropertyNames = CurrentObject.value().getPropertyNames(runtime); } - Continuation(facebook::jsi::Array &&value) noexcept - : Action(ContinuationAction::MoveToNextArrayElement), - CurrentArray(std::make_optional(std::move(value))) {} + Container(facebook::jsi::Array &&value) noexcept + : Type(ContainerType::Array), CurrentArray(std::make_optional(std::move(value))) {} - Continuation(const Continuation &) = delete; - Continuation(Continuation &&) = default; + Container(const Container &) = delete; + Container(Container &&) = default; }; - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER - static const int64_t MinSafeInteger = -9007199254740991L; - static const int64_t MaxSafeInteger = 9007199254740991L; - private: void SetValue(const facebook::jsi::Value &value) noexcept; @@ -61,7 +55,7 @@ struct JsiReader : implements { // 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 m_currentPrimitiveValue; - std::vector m_nonPrimitiveValues; + std::vector m_containers; }; } // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative/JsiWriter.cpp b/vnext/Microsoft.ReactNative/JsiWriter.cpp index 0e36579648b..0d2aee5ec8e 100644 --- a/vnext/Microsoft.ReactNative/JsiWriter.cpp +++ b/vnext/Microsoft.ReactNative/JsiWriter.cpp @@ -12,19 +12,14 @@ namespace winrt::Microsoft::ReactNative { //=========================================================================== JsiWriter::JsiWriter(facebook::jsi::Runtime &runtime) noexcept : m_runtime(runtime) { - Push({ContinuationAction::AcceptValueAndFinish}); + Push({ContainerState::AcceptValueAndFinish}); } facebook::jsi::Value JsiWriter::MoveResult() noexcept { - VerifyElseCrash(m_continuations.size() == 0); + VerifyElseCrash(m_containers.size() == 0); return std::move(m_result); } -facebook::jsi::Value JsiWriter::CopyResult() noexcept { - VerifyElseCrash(m_continuations.size() == 0); - return {m_runtime, m_result}; -} - void JsiWriter::WriteNull() noexcept { WriteValue(facebook::jsi::Value::null()); } @@ -34,14 +29,7 @@ void JsiWriter::WriteBoolean(bool value) noexcept { } void JsiWriter::WriteInt64(int64_t value) noexcept { - // JavaScript's integer is not int64_t, need to ensure that the value is in range - VerifyElseCrash(MinSafeInteger <= value && value <= MaxSafeInteger); - - // unfortunately JSI only supports int and double for number, choose double here - // make sure that doing type conversion in C++ makes no data loss - double d = static_cast(value); - VerifyElseCrash(floor(d) == d); - WriteValue(d); + WriteValue({static_cast(value)}); } void JsiWriter::WriteDouble(double value) noexcept { @@ -54,38 +42,38 @@ void JsiWriter::WriteString(const winrt::hstring &value) noexcept { void JsiWriter::WriteObjectBegin() noexcept { // legal to create an object when it is accepting a value - VerifyElseCrash(Top().Action != ContinuationAction::AcceptPropertyName); - Push({ContinuationAction::AcceptPropertyName, {m_runtime, facebook::jsi::Object(m_runtime)}}); + 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.Action == ContinuationAction::AcceptPropertyName); - top.Action = ContinuationAction::AcceptPropertyValue; + 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().Action == ContinuationAction::AcceptPropertyName); - auto createdObject = std::move(Pop().Values.at(0)); - WriteValue(std::move(createdObject)); + 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().Action != ContinuationAction::AcceptPropertyName); - Push({ContinuationAction::AcceptArrayElement}); + 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.Action == ContinuationAction::AcceptArrayElement); - facebook::jsi::Array createdArray(m_runtime, top.Values.size()); - for (size_t i = 0; i < top.Values.size(); i++) { - createdArray.setValueAtIndex(m_runtime, i, std::move(top.Values.at(i))); + 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}); @@ -93,21 +81,21 @@ void JsiWriter::WriteArrayEnd() noexcept { void JsiWriter::WriteValue(facebook::jsi::Value &&value) noexcept { auto &top = Top(); - switch (top.Action) { - case ContinuationAction::AcceptValueAndFinish: { + switch (top.State) { + case ContainerState::AcceptValueAndFinish: { m_result = std::move(value); Pop(); - VerifyElseCrash(m_continuations.size() == 0); + VerifyElseCrash(m_containers.size() == 0); break; } - case ContinuationAction::AcceptArrayElement: { - top.Values.push_back(std::move(value)); + case ContainerState::AcceptArrayElement: { + top.CurrentArrayElements.push_back(std::move(value)); break; } - case ContinuationAction::AcceptPropertyValue: { - auto createdObject = top.Values.at(0).getObject(m_runtime); + case ContainerState::AcceptPropertyValue: { + auto &createdObject = top.CurrentObject.value(); createdObject.setProperty(m_runtime, top.PropertyName.c_str(), std::move(value)); - top.Action = ContinuationAction::AcceptPropertyName; + top.State = ContainerState::AcceptPropertyName; top.PropertyName = {}; break; } @@ -116,19 +104,19 @@ void JsiWriter::WriteValue(facebook::jsi::Value &&value) noexcept { } } -JsiWriter::Continuation &JsiWriter::Top() noexcept { - VerifyElseCrash(m_continuations.size() > 0); - return m_continuations[m_continuations.size() - 1]; +JsiWriter::Container &JsiWriter::Top() noexcept { + VerifyElseCrash(m_containers.size() > 0); + return m_containers[m_containers.size() - 1]; } -JsiWriter::Continuation JsiWriter::Pop() noexcept { +JsiWriter::Container JsiWriter::Pop() noexcept { auto top = std::move(Top()); - m_continuations.pop_back(); + m_containers.pop_back(); return top; } -void JsiWriter::Push(Continuation &&continuation) noexcept { - m_continuations.push_back(std::move(continuation)); +void JsiWriter::Push(Container &&container) noexcept { + m_containers.push_back(std::move(container)); } /*static*/ facebook::jsi::Value JsiWriter::ToJsiValue( diff --git a/vnext/Microsoft.ReactNative/JsiWriter.h b/vnext/Microsoft.ReactNative/JsiWriter.h index 57f99c65f43..73e46894736 100644 --- a/vnext/Microsoft.ReactNative/JsiWriter.h +++ b/vnext/Microsoft.ReactNative/JsiWriter.h @@ -10,7 +10,6 @@ namespace winrt::Microsoft::ReactNative { struct JsiWriter : winrt::implements { JsiWriter(facebook::jsi::Runtime &runtime) noexcept; facebook::jsi::Value MoveResult() noexcept; - facebook::jsi::Value CopyResult() noexcept; public: // IJSValueWriter void WriteNull() noexcept; @@ -28,41 +27,37 @@ struct JsiWriter : winrt::implements { static facebook::jsi::Value ToJsiValue(facebook::jsi::Runtime &runtime, JSValueArgWriter const &argWriter) noexcept; private: - enum class ContinuationAction { + enum class ContainerState { AcceptValueAndFinish, AcceptArrayElement, AcceptPropertyName, AcceptPropertyValue, }; - struct Continuation { - ContinuationAction Action; - std::vector Values; + struct Container { + ContainerState State; + std::optional CurrentObject; + std::vector CurrentArrayElements; std::string PropertyName; - Continuation(ContinuationAction action) noexcept : Action(action) {} - Continuation(ContinuationAction action, facebook::jsi::Value &&value) noexcept : Action(action) { - Values.push_back(std::move(value)); - } + Container(ContainerState state) noexcept : State(state) {} + Container(ContainerState state, facebook::jsi::Object &&value) noexcept + : State(state), CurrentObject(std::move(value)) {} - Continuation(const Continuation &) = delete; - Continuation(Continuation &&) = default; + Container(const Container &) = delete; + Container(Container &&) = default; }; - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER - static const int64_t MinSafeInteger = -9007199254740991L; - static const int64_t MaxSafeInteger = 9007199254740991L; - private: void WriteValue(facebook::jsi::Value &&value) noexcept; - Continuation &Top() noexcept; - Continuation Pop() noexcept; - void Push(Continuation &&continuation) noexcept; + Container &Top() noexcept; + Container Pop() noexcept; + void Push(Container &&container) noexcept; private: facebook::jsi::Runtime &m_runtime; facebook::jsi::Value m_result; - std::vector m_continuations; + std::vector m_containers; }; } // namespace winrt::Microsoft::ReactNative