diff --git a/vnext/ReactUWP/Base/UwpReactInstance.cpp b/vnext/ReactUWP/Base/UwpReactInstance.cpp index 9b925f011b0..63726e9da4e 100644 --- a/vnext/ReactUWP/Base/UwpReactInstance.cpp +++ b/vnext/ReactUWP/Base/UwpReactInstance.cpp @@ -64,6 +64,11 @@ #include #include +#if !defined(OSS_RN) +#include +#include +#endif + #if !defined(OSS_RN) #include "ChakraJSIRuntimeHolder.h" #endif @@ -273,7 +278,20 @@ void UwpReactInstance::Start(const std::shared_ptr& spThis, cons #if !defined(OSS_RN) if (settings.UseJsi) - devSettings->jsiRuntimeHolder = std::make_shared(devSettings, jsQueue, nullptr, nullptr); + { + std::unique_ptr scriptStore = nullptr; + std::unique_ptr preparedScriptStore = nullptr; + + if (settings.EnableByteCodeCacheing || !settings.ByteCodeFileUri.empty()) { + scriptStore = std::make_unique(); + preparedScriptStore = std::make_unique(winrt::to_hstring(settings.ByteCodeFileUri)); + } + devSettings->jsiRuntimeHolder = std::make_shared( + devSettings, + jsQueue, + std::move(scriptStore), + std::move(preparedScriptStore)); + } #endif try diff --git a/vnext/ReactUWP/ReactUWP.vcxproj b/vnext/ReactUWP/ReactUWP.vcxproj index 42da9cba50b..94007881fe3 100644 --- a/vnext/ReactUWP/ReactUWP.vcxproj +++ b/vnext/ReactUWP/ReactUWP.vcxproj @@ -167,6 +167,8 @@ + + @@ -250,6 +252,8 @@ + + diff --git a/vnext/ReactUWP/ReactUWP.vcxproj.filters b/vnext/ReactUWP/ReactUWP.vcxproj.filters index 74bdcec1dce..006820b775a 100644 --- a/vnext/ReactUWP/ReactUWP.vcxproj.filters +++ b/vnext/ReactUWP/ReactUWP.vcxproj.filters @@ -216,6 +216,11 @@ Views + + Utils + + + Utils Views\Image @@ -483,6 +488,12 @@ Views\Image + + Utils + + + Utils + diff --git a/vnext/ReactUWP/UwpPreparedScriptStore.cpp b/vnext/ReactUWP/UwpPreparedScriptStore.cpp new file mode 100644 index 00000000000..023951cef39 --- /dev/null +++ b/vnext/ReactUWP/UwpPreparedScriptStore.cpp @@ -0,0 +1,108 @@ +#include "pch.h" +#include "UwpPreparedScriptStore.h" +#include +#include +#include "unicode.h" +#include "jsi/jsi.h" + +#if _MSC_VER <= 1913 +// VC 19 (2015-2017.6) cannot optimize co_await/cppwinrt usage +#pragma optimize( "", off ) +#endif + +namespace winrt { + using namespace winrt::Windows::Foundation; + using namespace winrt::Windows::Storage; +}; + +namespace react { namespace uwp { + UwpPreparedScriptStore::UwpPreparedScriptStore(winrt::hstring uri) + { + if (!uri.empty()) + { + m_byteCodeFileAsync = winrt::StorageFile::GetFileFromApplicationUriAsync(winrt::Uri(uri)); + } + } + +std::unique_ptr UwpPreparedScriptStore::tryGetPreparedScript( + const facebook::jsi::ScriptSignature& scriptSignature, + const facebook::jsi::JSRuntimeSignature& runtimeSignature, + const char* prepareTag // Optional tag. For e.g. eagerly evaluated vs lazy cache. +) noexcept +{ + try { + + // check if app bundle version is older than or equal to the prepared script version + // if true then just read the buffer from the prepared script and return + auto byteCodeFile = TryGetByteCodeFileSync(scriptSignature); + if (byteCodeFile == nullptr) { + return nullptr; + } + + auto buffer = winrt::FileIO::ReadBufferAsync(byteCodeFile).get(); + auto bytecodeBuffer(std::make_unique(buffer.Length())); + auto dataReader{ winrt::Streams::DataReader::FromBuffer(buffer) }; + dataReader.ReadBytes(winrt::array_view { &bytecodeBuffer->data()[0], &bytecodeBuffer->data()[bytecodeBuffer->size()] }); + dataReader.Close(); + + return bytecodeBuffer; + } + catch (...) { + return nullptr; + } +} + +void UwpPreparedScriptStore::persistPreparedScript( + std::shared_ptr preparedScript, + const facebook::jsi::ScriptSignature& scriptMetadata, + const facebook::jsi::JSRuntimeSignature& runtimeMetadata, + const char* prepareTag // Optional tag. For e.g. eagerly evaluated vs lazy cache. +) noexcept +{ + persistPreparedScriptAsync(preparedScript, scriptMetadata, runtimeMetadata, prepareTag); +} + +winrt::fire_and_forget UwpPreparedScriptStore::persistPreparedScriptAsync( + std::shared_ptr preparedScript, + const facebook::jsi::ScriptSignature& scriptMetadata, + const facebook::jsi::JSRuntimeSignature& runtimeMetadata, + const char* prepareTag // Optional tag. For e.g. eagerly evaluated vs lazy cache. +) +{ + try { + co_await winrt::resume_background(); + auto folder = winrt::ApplicationData::Current().LocalCacheFolder(); + auto fileName = winrt::to_hstring(scriptMetadata.url + ".bytecode"); + auto file = co_await folder.CreateFileAsync(fileName, winrt::CreationCollisionOption::ReplaceExisting); + winrt::FileIO::WriteBytesAsync(file, winrt::array_view{ &preparedScript->data()[0], &preparedScript->data()[preparedScript->size()] }); + } + catch (...) { + } +} + +winrt::StorageFile UwpPreparedScriptStore::TryGetByteCodeFileSync(const facebook::jsi::ScriptSignature& scriptSignature) +{ + try { + if (m_byteCodeFileAsync != nullptr) { + auto file = m_byteCodeFileAsync.get(); + auto fileprops = file.GetBasicPropertiesAsync().get(); + facebook::jsi::ScriptVersion_t byteCodeVersion = fileprops.DateModified().time_since_epoch().count(); + if (byteCodeVersion >= scriptSignature.version) { + return file; + } + } + } + catch (...) { + // Eat this exception. If we can't get the file from the uri. Still try looking in the cache. + } + + // Getting here means one of two things. No bytecode file uri was specified, or the file uri was specified but it is outdated. + // Try looking in LocalCache folder for bytecode file and use that. + auto fileName = winrt::to_hstring(scriptSignature.url + ".bytecode"); + auto file = winrt::ApplicationData::Current().LocalCacheFolder().GetFileAsync(fileName).get(); + auto fileprops = file.GetBasicPropertiesAsync().get(); + facebook::jsi::ScriptVersion_t byteCodeVersion = fileprops.DateModified().time_since_epoch().count(); + + return byteCodeVersion > scriptSignature.version ? file : nullptr; +} +}} diff --git a/vnext/ReactUWP/UwpPreparedScriptStore.h b/vnext/ReactUWP/UwpPreparedScriptStore.h new file mode 100644 index 00000000000..acab5ac9873 --- /dev/null +++ b/vnext/ReactUWP/UwpPreparedScriptStore.h @@ -0,0 +1,64 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include "jsi/jsi.h" + +namespace react { namespace uwp { +class UwpPreparedScriptStore : public facebook::jsi::PreparedScriptStore +{ +public: + UwpPreparedScriptStore(winrt::hstring uri); + std::unique_ptr tryGetPreparedScript( + const facebook::jsi::ScriptSignature& scriptSignature, + const facebook::jsi::JSRuntimeSignature& runtimeSignature, + const char* prepareTag // Optional tag. For e.g. eagerly evaluated vs lazy cache. + ) noexcept override; + + void persistPreparedScript( + std::shared_ptr preparedScript, + const facebook::jsi::ScriptSignature& scriptMetadata, + const facebook::jsi::JSRuntimeSignature& runtimeMetadata, + const char* prepareTag // Optional tag. For e.g. eagerly evaluated vs lazy cache. + ) noexcept override; + + UwpPreparedScriptStore(const UwpPreparedScriptStore&) = delete; + void operator=(const UwpPreparedScriptStore&) = delete; +private: + winrt::fire_and_forget persistPreparedScriptAsync( + std::shared_ptr preparedScript, + const facebook::jsi::ScriptSignature& scriptMetadata, + const facebook::jsi::JSRuntimeSignature& runtimeMetadata, + const char* prepareTag // Optional tag. For e.g. eagerly evaluated vs lazy cache. + ); + winrt::Windows::Storage::StorageFile TryGetByteCodeFileSync(const facebook::jsi::ScriptSignature& scriptSignature); + winrt::Windows::Foundation::IAsyncOperation m_byteCodeFileAsync; +}; + +// This is very similiar to ByteArrayBuffer in ChakraJsiRuntime.h. +// Defining this to avoid referencing types in chakra headers +class ByteCodeBuffer final : public facebook::jsi::Buffer { +public: + size_t size() const override { + return size_; + } + const uint8_t* data() const { + return byteArray_.get(); + } + + uint8_t* data() { + return byteArray_.get(); + } + + ByteCodeBuffer(int size) : size_(size), byteArray_(std::make_unique(size)) {} + ByteCodeBuffer(const ByteCodeBuffer&) = delete; + void operator=(const ByteCodeBuffer&) = delete; + +private: + int size_; + std::unique_ptr byteArray_; +}; +}} diff --git a/vnext/ReactUWP/UwpScriptStore.cpp b/vnext/ReactUWP/UwpScriptStore.cpp new file mode 100644 index 00000000000..1dfa81017ee --- /dev/null +++ b/vnext/ReactUWP/UwpScriptStore.cpp @@ -0,0 +1,54 @@ +#include "pch.h" +#include "UwpScriptStore.h" +#include +#include +#include +#include +#include "unicode.h" + +namespace winrt { + using namespace winrt::Windows::Foundation; + using namespace winrt::Windows::Storage; +} + +namespace react { namespace uwp { + +UwpScriptStore::UwpScriptStore() {} + +facebook::jsi::VersionedBuffer UwpScriptStore::getVersionedScript(const std::string& url) noexcept +{ + facebook::jsi::VersionedBuffer versionedBuffer_; + versionedBuffer_.buffer = nullptr; + versionedBuffer_.version = 0; + + return versionedBuffer_; +} + +// Script version = timestamp of bundle file last created +facebook::jsi::ScriptVersion_t UwpScriptStore::getScriptVersion(const std::string& url) noexcept +{ + const std::string bundleUrl = "ms-appx:///Bundle/" + url + ".bundle"; + const winrt::DateTime bundleModifiedTime = getBundleModifiedDate(bundleUrl).get(); + const std::uint64_t timestamp = bundleModifiedTime.time_since_epoch().count(); + return timestamp; +} + +std::future UwpScriptStore::getBundleModifiedDate(const std::string& bundleUri) +{ + winrt::hstring str(facebook::react::unicode::utf8ToUtf16(bundleUri)); + winrt::Windows::Foundation::Uri uri(str); + + try + { + auto file = co_await winrt::StorageFile::GetFileFromApplicationUriAsync(uri); + auto props = file.GetBasicPropertiesAsync().get(); + return props.DateModified(); + } + catch (winrt::hresult_error const& ex) + { + winrt::DateTime date; + return date; + } +} + +}} diff --git a/vnext/ReactUWP/UwpScriptStore.h b/vnext/ReactUWP/UwpScriptStore.h new file mode 100644 index 00000000000..b30d627cf13 --- /dev/null +++ b/vnext/ReactUWP/UwpScriptStore.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +namespace react { namespace uwp { + +class UwpScriptStore : public facebook::jsi::ScriptStore +{ +public: + facebook::jsi::VersionedBuffer getVersionedScript(const std::string& url) noexcept override; + facebook::jsi::ScriptVersion_t getScriptVersion(const std::string& url) noexcept override; + UwpScriptStore(); + UwpScriptStore(const UwpScriptStore&) = delete; + void operator=(const UwpScriptStore&) = delete; +private: + std::future getBundleModifiedDate(const std::string& bundlePath); +}; + +}} diff --git a/vnext/include/ReactUWP/IReactInstance.h b/vnext/include/ReactUWP/IReactInstance.h index 0071a563cef..9ec23d5bb3c 100644 --- a/vnext/include/ReactUWP/IReactInstance.h +++ b/vnext/include/ReactUWP/IReactInstance.h @@ -31,6 +31,9 @@ struct ReactInstanceSettings bool UseDirectDebugger{ false }; bool UseJsi { true }; bool EnableJITCompilation { true }; + bool EnableByteCodeCacheing { false }; + + std::string ByteCodeFileUri; std::string DebugHost; std::string DebugBundlePath; facebook::react::NativeLoggingHook LoggingCallback; diff --git a/vnext/package.json b/vnext/package.json index 23657e55416..178b730c10e 100644 --- a/vnext/package.json +++ b/vnext/package.json @@ -72,4 +72,4 @@ "react": "16.8.3", "react-native": "^0.59.0 || 0.59.0-microsoft.5 || https://github.com/microsoft/react-native/archive/v0.59.0-microsoft.5.tar.gz" } -} \ No newline at end of file +}