diff --git a/change/react-native-windows-2020-05-21-13-01-25-WeakModule.json b/change/react-native-windows-2020-05-21-13-01-25-WeakModule.json new file mode 100644 index 00000000000..c39f097a0f9 --- /dev/null +++ b/change/react-native-windows-2020-05-21-13-01-25-WeakModule.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "Implemented support for native module std::weak_ptr", + "packageName": "react-native-windows", + "email": "vmorozov@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-05-21T20:01:24.822Z" +} diff --git a/change/react-native-windows-2020-05-21-22-04-55-PR-FixInstanceLoading.json b/change/react-native-windows-2020-05-21-22-04-55-PR-FixInstanceLoading.json new file mode 100644 index 00000000000..3747500288b --- /dev/null +++ b/change/react-native-windows-2020-05-21-22-04-55-PR-FixInstanceLoading.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "Fix ReactInstance error state to avoid crashes", + "packageName": "react-native-windows", + "email": "vmorozov@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-05-22T05:04:55.630Z" +} diff --git a/change/react-native-windows-2020-05-25-15-56-21-PR-UIDispatcherProperty.json b/change/react-native-windows-2020-05-25-15-56-21-PR-UIDispatcherProperty.json new file mode 100644 index 00000000000..fc7f7bb93c4 --- /dev/null +++ b/change/react-native-windows-2020-05-25-15-56-21-PR-UIDispatcherProperty.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "Add UIDispatcher property to ReactInstanceSettings and IReactContext", + "packageName": "react-native-windows", + "email": "vmorozov@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-05-25T22:56:21.049Z" +} diff --git a/packages/microsoft-reactnative-sampleapps/windows/SampleLibraryCPP/SampleModuleCPP.h b/packages/microsoft-reactnative-sampleapps/windows/SampleLibraryCPP/SampleModuleCPP.h index 161adc8f9c8..c8019c348bd 100644 --- a/packages/microsoft-reactnative-sampleapps/windows/SampleLibraryCPP/SampleModuleCPP.h +++ b/packages/microsoft-reactnative-sampleapps/windows/SampleLibraryCPP/SampleModuleCPP.h @@ -10,7 +10,7 @@ #include "DebugHelpers.h" #include "NativeModules.h" -#define DEBUG_OUTPUT(...) DebugWriteLine("SampleModuleCppImpl", ##__VA_ARGS__); +#define DEBUG_OUTPUT(...) DebugWriteLine("SampleLibraryCpp", ##__VA_ARGS__); namespace SampleLibraryCpp { @@ -39,6 +39,7 @@ struct SampleModuleCppImpl { DEBUG_OUTPUT("C++ Properties.Prop1:", *reactContext.Properties().Get(myProp1)); DEBUG_OUTPUT("C++ Properties.Prop2:", winrt::to_string(*reactContext.Properties().Get(myProp2))); + // Note that all notification subscriptions are removed automatically when React instance is unloaded. m_timer = winrt::Windows::System::Threading::ThreadPoolTimer::CreatePeriodicTimer( [this](const winrt::Windows::System::Threading::ThreadPoolTimer) noexcept { TimedEvent(++m_timerCount); @@ -246,4 +247,22 @@ struct SampleModuleCppImpl { static constexpr std::chrono::milliseconds TimedEventInterval{5000}; }; +// SampleSharedCppModule shows how to inherited native modules from std::enable_shared_from_this +// to use weak_from_this() in event handlers. In this example we use notifications instead +// of events just to show case the std::weak_ptr use. +REACT_MODULE(SampleSharedCppModule); +struct SampleSharedCppModule : std::enable_shared_from_this { + using IInspectable = winrt::Windows::Foundation::IInspectable; + + // The Initialize method is called when React instance loaded JavaScript and the module is ready to use. + REACT_INIT(Initialize) + void Initialize(React::ReactContext const & /*reactContext*/) noexcept {} + + REACT_METHOD(SayHello) + std::string SayHello() noexcept { + // This method is currently unused + return "Hello"; + } +}; + } // namespace SampleLibraryCpp diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj b/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj index 2a6954d74c6..b88b4fb1f80 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.vcxproj @@ -141,12 +141,13 @@ + + + - - - + diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp index 9dd3b8054e0..ad42d8ec369 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp @@ -12,6 +12,10 @@ struct ReactContextStub : implements { VerifyElseCrashSz(false, "Not implemented"); } + IReactDispatcher UIDispatcher() noexcept { + VerifyElseCrashSz(false, "Not implemented"); + } + void DispatchEvent( Windows::UI::Xaml::FrameworkElement const & /*view*/, hstring const & /*eventName*/, diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h index 01ffc906a3d..275780c7582 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h @@ -116,6 +116,10 @@ struct ReactContextMock : implements { VerifyElseCrashSz(false, "Not implemented"); } + IReactDispatcher UIDispatcher() noexcept { + VerifyElseCrashSz(false, "Not implemented"); + } + void DispatchEvent( FrameworkElement const & /*view*/, hstring const & /*eventName*/, diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/pch.h b/vnext/Microsoft.ReactNative.Cxx.UnitTests/pch.h index 06661b920e8..a6f6676eca7 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/pch.h +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/pch.h @@ -10,9 +10,9 @@ #undef GetCurrentTime +#include #include #include -#include "winrt/Microsoft.ReactNative.h" #include "gtest/gtest.h" #include "motifCpp/gTestAdapter.h" diff --git a/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems b/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems index c13d1931510..bffaa12db88 100644 --- a/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems +++ b/vnext/Microsoft.ReactNative.Cxx/Microsoft.ReactNative.Cxx.vcxitems @@ -15,6 +15,7 @@ + @@ -23,6 +24,7 @@ + diff --git a/vnext/Microsoft.ReactNative.Cxx/NativeModules.h b/vnext/Microsoft.ReactNative.Cxx/NativeModules.h index 6d76e18f4ca..2fece26c752 100644 --- a/vnext/Microsoft.ReactNative.Cxx/NativeModules.h +++ b/vnext/Microsoft.ReactNative.Cxx/NativeModules.h @@ -2,7 +2,8 @@ // Licensed under the MIT License. #pragma once -#include "winrt/Microsoft.ReactNative.h" +#include +#include #include "JSValueReader.h" #include "JSValueWriter.h" @@ -1108,29 +1109,77 @@ struct TurboModuleSpec { } }; +// The default factory for the TModule. +// It wraps up the TModule into a ReactNonAbiValue to be passed through the ABI boundary. +template +inline std::tuple MakeDefaultReactModuleWrapper() noexcept { + ReactNonAbiValue moduleWrapper{std::in_place}; + TModule *module = moduleWrapper.GetPtr(); + return std::tuple{std::move(moduleWrapper), module}; +} + +// The default factory for TModule inherited from enable_shared_from_this. +// It wraps up the TModule into an std::shared_ptr before giving it to ReactNonAbiValue. +template +inline std::tuple +MakeDefaultSharedPtrReactModuleWrapper() noexcept { + ReactNonAbiValue> moduleWrapper{std::in_place, std::make_shared()}; + TModule *module = moduleWrapper.GetPtr()->get(); + return std::tuple{std::move(moduleWrapper), module}; +} + +namespace Internal { +// Internal functions to test if type is inherited from std::enable_shared_from_this. +template +std::true_type IsBaseOfTemplateTest(std::enable_shared_from_this *); +std::false_type IsBaseOfTemplateTest(...); +} // namespace Internal + +// Check if type T is inherited from std::enable_shared_form_this. +// We support the scenario when the T and U are different types. +template +using IsEnabledSharedFromThisT = decltype(Internal::IsBaseOfTemplateTest((T *)nullptr)); +template +inline constexpr bool IsEnabledSharedFromThisV = IsEnabledSharedFromThisT::value; + +// Default implementation of factory getter for a TModule type **not** inherited from std::enable_shared_form_this. +// For the custom implementation define GetReactModuleFactory with the last parameter to be 'int' (not 'int *'). +template , int> = 0> +inline constexpr auto GetReactModuleFactory(TModule * /*moduleNullPtr*/, int * /*useDefault*/) noexcept { + return &MakeDefaultReactModuleWrapper; +} + +// Default implementation of factory getter for a TModule type inherited from std::enable_shared_form_this. +// For the custom implementation define GetReactModuleFactory with the last parameter to be 'int' (not 'int *'). +template , int> = 0> +inline constexpr auto GetReactModuleFactory(TModule * /*moduleNullPtr*/, int * /*useDefault*/) noexcept { + return &MakeDefaultSharedPtrReactModuleWrapper; +} + +// Type traits for TModule. It defines a factory to create the module and its ABI-safe wrapper. +template +struct ReactModuleTraits { + using FactoryType = std::tuple() noexcept; + static constexpr FactoryType *Factory = GetReactModuleFactory((TModule *)nullptr, 0); +}; + +// Create a module provider for TModule type. template inline ReactModuleProvider MakeModuleProvider() noexcept { return [](IReactModuleBuilder const &moduleBuilder) noexcept { - ReactNonAbiValue moduleObject{std::in_place}; - TModule *module = moduleObject.GetPtr(); + auto [moduleWrapper, module] = ReactModuleTraits::Factory(); ReactModuleBuilder builder{module, moduleBuilder}; GetReactModuleInfo(module, builder); builder.CompleteRegistration(); - return moduleObject; + return moduleWrapper; }; } +// Create a module provider for TModule type that satisfies the TModuleSpec. template inline ReactModuleProvider MakeTurboModuleProvider() noexcept { TModuleSpec::template ValidateModule(); - return [](IReactModuleBuilder const &moduleBuilder) noexcept { - ReactNonAbiValue moduleObject{std::in_place}; - TModule *module = moduleObject.GetPtr(); - ReactModuleBuilder builder{module, moduleBuilder}; - GetReactModuleInfo(module, builder); - builder.CompleteRegistration(); - return moduleObject; - }; + return MakeModuleProvider(); } } // namespace winrt::Microsoft::ReactNative diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactContext.h b/vnext/Microsoft.ReactNative.Cxx/ReactContext.h index c1b202b3051..1bec8cb8f50 100644 --- a/vnext/Microsoft.ReactNative.Cxx/ReactContext.h +++ b/vnext/Microsoft.ReactNative.Cxx/ReactContext.h @@ -7,6 +7,7 @@ #include #include "JSValueWriter.h" +#include "ReactDispatcher.h" #include "ReactPropertyBag.h" namespace winrt::Microsoft::ReactNative { @@ -24,13 +25,17 @@ struct ReactContext { } explicit operator bool() const noexcept { - return static_cast(m_handle); + return m_handle ? true : false; } ReactPropertyBag Properties() const noexcept { return ReactPropertyBag{m_handle.Properties()}; } + ReactDispatcher UIDispatcher() const noexcept { + return ReactDispatcher{m_handle.UIDispatcher()}; + } + // Call methodName JS function of module with moduleName. // args are either function arguments or a single lambda with 'IJSValueWriter const&' argument. template diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactDispatcher.h b/vnext/Microsoft.ReactNative.Cxx/ReactDispatcher.h new file mode 100644 index 00000000000..a28e4b09468 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactDispatcher.h @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once +#ifndef MICROSOFT_REACTNATIVE_REACTDISPATCHER +#define MICROSOFT_REACTNATIVE_REACTDISPATCHER + +#include +#include "ReactHandleHelper.h" + +namespace winrt::Microsoft::ReactNative { + +// Represents a dispatcher queue to invoke work item asynchronously. +// It wraps up the IReactDispatcher and adds convenience methods for +// working with C++ types. +struct ReactDispatcher { + ReactDispatcher(std::nullptr_t = nullptr) noexcept {} + + explicit ReactDispatcher(IReactDispatcher const &handle) noexcept : m_handle{handle} {} + + IReactDispatcher const &Handle() const noexcept { + return m_handle; + } + + explicit operator bool() const noexcept { + return m_handle ? true : false; + } + + void Post(ReactDispatcherCallback const &callback) const noexcept { + if (m_handle) { + m_handle.Post(callback); + } + } + + bool HasThreadAccess() const noexcept { + return m_handle ? m_handle.HasThreadAccess() : false; + } + + static ReactDispatcher CreateSerialDispatcher() noexcept { + return ReactDispatcher{ReactDispatcherHelper::CreateSerialDispatcher()}; + } + + private: + IReactDispatcher m_handle; +}; + +} // namespace winrt::Microsoft::ReactNative + +#endif // MICROSOFT_REACTNATIVE_REACTDISPATCHER diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactHandleHelper.h b/vnext/Microsoft.ReactNative.Cxx/ReactHandleHelper.h new file mode 100644 index 00000000000..5b6209d4017 --- /dev/null +++ b/vnext/Microsoft.ReactNative.Cxx/ReactHandleHelper.h @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once +#ifndef MICROSOFT_REACTNATIVE_REACTHANDLEHELPER +#define MICROSOFT_REACTNATIVE_REACTHANDLEHELPER + +// +// Helper methods for types that have Handle() method that return +// an IInspectable-inherited value. +// + +#include +#include + +namespace winrt::Microsoft::ReactNative { + +namespace Internal { +template +auto TestHandle(int) -> decltype(std::declval().Handle()); +template +auto TestHandle(int *) -> void; +} // namespace Internal + +template +inline constexpr bool HasHandleV = + std::is_base_of_v(0))>>; + +// True if two types with Handle() have the same handle. +template , int> = 0> +inline bool operator==(T const &left, T const &right) noexcept { + return left.Handle() == right.Handle(); +} + +// True if two types with Handle() have different handles. +template , int> = 0> +inline bool operator!=(T const &left, T const &right) noexcept { + return !(left.Handle() == right.Handle()); +} + +// True if handle of left is null. +template , int> = 0> +inline bool operator==(T const &left, std::nullptr_t) noexcept { + return !static_cast(left.Handle()); +} + +// True if handle of left is not null. +template , int> = 0> +inline bool operator!=(T const &left, std::nullptr_t) noexcept { + return static_cast(left.Handle()); +} + +// True if handle of right is null. +template , int> = 0> +inline bool operator==(std::nullptr_t, T const &right) noexcept { + return !static_cast(right.Handle()); +} + +// True if handle of left is not null. +template , int> = 0> +inline bool operator!=(std::nullptr_t, T const &right) noexcept { + return static_cast(right.Handle()); +} + +} // namespace winrt::Microsoft::ReactNative + +#endif // MICROSOFT_REACTNATIVE_REACTHANDLEHELPER diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactPropertyBag.h b/vnext/Microsoft.ReactNative.Cxx/ReactPropertyBag.h index 8d461e9fb12..d7a05869c3a 100644 --- a/vnext/Microsoft.ReactNative.Cxx/ReactPropertyBag.h +++ b/vnext/Microsoft.ReactNative.Cxx/ReactPropertyBag.h @@ -2,8 +2,8 @@ // Licensed under the MIT License. #pragma once -#ifndef MICROSOFT_REACTNATIVE_PROPERTYBAG -#define MICROSOFT_REACTNATIVE_PROPERTYBAG +#ifndef MICROSOFT_REACTNATIVE_REACTPROPERTYBAG +#define MICROSOFT_REACTNATIVE_REACTPROPERTYBAG // // ReactPropertyBag is a thread-safe storage of property values. @@ -69,77 +69,89 @@ #include #include +#include "ReactHandleHelper.h" #include "ReactNonAbiValue.h" namespace winrt::Microsoft::ReactNative { -// Encapsulates the IReactPropertyNamespace +// ReactPropertyNamespace encapsulates the IReactPropertyNamespace. +// It represents an atomic property namespace object. struct ReactPropertyNamespace { ReactPropertyNamespace(std::nullptr_t = nullptr) noexcept {} explicit ReactPropertyNamespace(IReactPropertyNamespace const &handle) noexcept : m_handle{handle} {} - explicit ReactPropertyNamespace(param::hstring const &ns) noexcept - : m_handle{ReactPropertyBagHelper::GetNamespace(ns)} {} + explicit ReactPropertyNamespace(param::hstring const &namespaceName) noexcept + : m_handle{ReactPropertyBagHelper::GetNamespace(namespaceName)} {} - static ReactPropertyNamespace Global() noexcept { - return ReactPropertyNamespace{ReactPropertyBagHelper::GlobalNamespace()}; + IReactPropertyNamespace const &Handle() const noexcept { + return m_handle; } - hstring NamespaceName() const noexcept { - return m_handle ? m_handle.NamespaceName() : hstring{}; + explicit operator bool() const noexcept { + return m_handle ? true : false; } - IReactPropertyNamespace const &Handle() const noexcept { - return m_handle; + static ReactPropertyNamespace Global() noexcept { + return ReactPropertyNamespace{ReactPropertyBagHelper::GlobalNamespace()}; } - explicit operator bool() const noexcept { - return static_cast(m_handle); + hstring NamespaceName() const noexcept { + return m_handle ? m_handle.NamespaceName() : hstring{}; } private: IReactPropertyNamespace m_handle; }; -// Encapsulates the IReactPropertyName and the property type -template -struct ReactPropertyId { - using PropertyType = T; +// ReactPropertyName encapsulates the IReactPropertyName. +// It represents an atomic property name object that defines a LocalName +// within the referenced Namespace. +struct ReactPropertyName { + ReactPropertyName(std::nullptr_t = nullptr) noexcept {} - ReactPropertyId(std::nullptr_t = nullptr) noexcept {} + explicit ReactPropertyName(IReactPropertyName const &handle) noexcept : m_handle{handle} {} - explicit ReactPropertyId(IReactPropertyName const &handle) noexcept : m_handle{handle} {} - - explicit ReactPropertyId(hstring const &localName) noexcept + explicit ReactPropertyName(hstring const &localName) noexcept : m_handle{ReactPropertyBagHelper::GetName(nullptr, localName)} {} - ReactPropertyId(ReactPropertyNamespace const &ns, hstring const &localName) noexcept + ReactPropertyName(ReactPropertyNamespace const &ns, hstring const &localName) noexcept : m_handle{ReactPropertyBagHelper::GetName(ns.Handle(), localName)} {} - ReactPropertyId(hstring const &ns, hstring const &localName) noexcept - : m_handle{ReactPropertyBagHelper::GetName(ReactPropertyBagHelper::GetNamespace(ns), localName)} {} + ReactPropertyName(hstring const &namespaceName, hstring const &localName) noexcept + : m_handle{ReactPropertyBagHelper::GetName(ReactPropertyBagHelper::GetNamespace(namespaceName), localName)} {} - hstring NamespaceName() const noexcept { - return m_handle ? m_handle.Namespace().NamespaceName() : hstring{}; + IReactPropertyName const &Handle() const noexcept { + return m_handle; } - hstring LocalName() const noexcept { - return m_handle ? m_handle.LocalName() : hstring{}; + explicit operator bool() const noexcept { + return m_handle ? true : false; } - IReactPropertyName const &Handle() const noexcept { - return m_handle; + ReactPropertyNamespace Namespace() const noexcept { + return ReactPropertyNamespace{m_handle ? m_handle.Namespace() : nullptr}; } - explicit operator bool() const noexcept { - return static_cast(m_handle); + hstring NamespaceName() const noexcept { + return m_handle ? m_handle.Namespace().NamespaceName() : hstring{}; + } + + hstring LocalName() const noexcept { + return m_handle ? m_handle.LocalName() : hstring{}; } private: IReactPropertyName m_handle; }; +// Encapsulates the IReactPropertyName and the property type +template +struct ReactPropertyId : ReactPropertyName { + using PropertyType = T; + using ReactPropertyName::ReactPropertyName; +}; + // ReactPropertyBag is a wrapper for IReactPropertyBag to store strongly-typed properties in a thread-safe way. // Types inherited from IInspectable are stored directly. // Values of other types are boxed with help of winrt::box_value. @@ -159,7 +171,7 @@ struct ReactPropertyBag { // True if handle is not null. explicit operator bool() const noexcept { - return static_cast(m_handle); + return m_handle ? true : false; } // Get ReactPropertyBag handle. @@ -229,37 +241,7 @@ struct ReactPropertyBag { Remove(m_handle, propertyId); } - // True if two ReactPropertyBags have the same handle. - friend bool operator==(const ReactPropertyBag &left, const ReactPropertyBag &right) noexcept { - return left.m_handle == right.m_handle; - } - - // True if two ReactPropertyBags have different handles. - friend bool operator!=(const ReactPropertyBag &left, const ReactPropertyBag &right) noexcept { - return left.m_handle != right.m_handle; - } - - // True if left ReactPropertyBag handle is null. - friend bool operator==(const ReactPropertyBag &left, std::nullptr_t) noexcept { - return !static_cast(left.m_handle); - } - - // True if left ReactPropertyBag handle is not null. - friend bool operator!=(const ReactPropertyBag &left, std::nullptr_t) noexcept { - return static_cast(left.m_handle); - } - - // True if right ReactPropertyBag handle is null. - friend bool operator==(std::nullptr_t, const ReactPropertyBag &right) noexcept { - return !static_cast(right.m_handle); - } - - // True if right ReactPropertyBag handle is not null. - friend bool operator!=(std::nullptr_t, const ReactPropertyBag &right) noexcept { - return static_cast(right.m_handle); - } - - private: + // Box value to an ABI-safe object. template || std::is_same_v, int> = 0> static Windows::Foundation::IInspectable ToObject(TValue const &value) noexcept { // We box WinRT types and return IInspectable-inherited values as-is. @@ -267,6 +249,7 @@ struct ReactPropertyBag { return box_value(value); } + // Box value to an ABI-safe object. template && !std::is_same_v, int> = 0> static Windows::Foundation::IInspectable ToObject(TValue &&value) noexcept { // Create ReactNonAbiValue with newly allocated wrapper for U and pass TValue as an argument to the U @@ -275,11 +258,13 @@ struct ReactPropertyBag { return T{std::in_place, std::forward(value)}; } + // Box value to an ABI-safe object. template static Windows::Foundation::IInspectable ToObject(std::optional const &value) noexcept { return value ? ToObject(*value) : nullptr; } + // Unbox value from an ABI-safe object. template static auto FromObject(Windows::Foundation::IInspectable const &obj) noexcept { // The code mostly borrowed from the winrt::unbox_value_or implementation to return @@ -316,4 +301,4 @@ struct ReactPropertyBag { } // namespace winrt::Microsoft::ReactNative -#endif // MICROSOFT_REACTNATIVE_PROPERTYBAG +#endif // MICROSOFT_REACTNATIVE_REACTPROPERTYBAG diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj index c87bd444a9c..ccb601a5e14 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj +++ b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj @@ -107,6 +107,7 @@ + diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/ReactInstanceSettingsTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/ReactInstanceSettingsTests.cpp new file mode 100644 index 00000000000..9a0f48cbba8 --- /dev/null +++ b/vnext/Microsoft.ReactNative.IntegrationTests/ReactInstanceSettingsTests.cpp @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" + +#include +#include + +using namespace winrt; +using namespace Microsoft::ReactNative; +using namespace Windows::System; + +namespace ReactNativeIntegrationTests { + +TEST_CLASS (ReactInstanceSettingsTests) { + TEST_METHOD(DefaultUIDispatcher_NonUIThread) { + // In case if current thread has no ThreadDispatcher, then the + // ReactInstanceSettings::UIDispatcher is not initialized. + ReactInstanceSettings settings; + TestCheck(settings); + TestCheck(!settings.UIDispatcher()); + // UIDispatcher() is a shortcut for getting property value. + TestCheck(!settings.Properties().Get(ReactDispatcherHelper::UIDispatcherProperty())); + } + + TEST_METHOD(DefaultUIDispatcher_UIThread) { + // ReactInstanceSettings::UIDispatcher is set to a non-null value if it is + // creates from a UI thread. We simulate the UI thread with DispatcherQueueController. + auto queueController = DispatcherQueueController::CreateOnDedicatedThread(); + auto uiDispatcher = queueController.DispatcherQueue(); + TestCheck(uiDispatcher); + uiDispatcher.TryEnqueue([]() noexcept { + ReactInstanceSettings settings; + TestCheck(settings); + TestCheck(settings.UIDispatcher()); + // UIDispatcher() is a shortcut for getting property value. + TestCheck(settings.Properties().Get(ReactDispatcherHelper::UIDispatcherProperty())); + TestCheckEqual(ReactDispatcherHelper::UIThreadDispatcher(), settings.UIDispatcher()); + TestCheckEqual( + ReactDispatcherHelper::UIThreadDispatcher(), + settings.Properties().Get(ReactDispatcherHelper::UIDispatcherProperty())); + }); + queueController.ShutdownQueueAsync().get(); + } +}; + +} // namespace ReactNativeIntegrationTests diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/ReactPropertyBagTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/ReactPropertyBagTests.cpp index d7200d84715..9e1bd2c9f4c 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/ReactPropertyBagTests.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/ReactPropertyBagTests.cpp @@ -265,6 +265,21 @@ TEST_CLASS (ReactPropertyBagTests) { TestCheckEqual(ReactPropertyBagHelper::GlobalNamespace(), ReactPropertyNamespace::Global().Handle()); } + TEST_METHOD(PropertyNamespace_Equality) { + ReactPropertyNamespace ns11{L"Foo"}; + ReactPropertyNamespace ns12{ns11}; + ReactPropertyNamespace ns2{L"Bar"}; + ReactPropertyNamespace ns3; + TestCheckEqual(ns11, ns12); + TestCheckEqual(ns12, ns11); + TestCheck(ns11 != ns2); + TestCheck(ns2 != ns11); + TestCheck(ns2 != nullptr); + TestCheck(nullptr != ns2); + TestCheck(ns3 == nullptr); + TestCheck(nullptr == ns3); + } + TEST_METHOD(PropertyId_ctor_default) { ReactPropertyId name1; TestCheck(!name1); diff --git a/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs b/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs index 08e87dcfaf3..f59b12e71f9 100644 --- a/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs +++ b/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs @@ -307,6 +307,8 @@ public ReactContextMock(ReactModuleBuilderMock builder) public IReactPropertyBag Properties { get; } = ReactPropertyBagHelper.CreatePropertyBag(); + public IReactDispatcher UIDispatcher => Properties.Get(ReactDispatcherHelper.UIDispatcherProperty) as IReactDispatcher; + public void DispatchEvent(FrameworkElement view, string eventName, JSValueArgWriter eventDataArgWriter) { throw new NotImplementedException(); diff --git a/vnext/Microsoft.ReactNative.sln b/vnext/Microsoft.ReactNative.sln index d1fdaa3e7dc..4e8aef2068c 100644 --- a/vnext/Microsoft.ReactNative.sln +++ b/vnext/Microsoft.ReactNative.sln @@ -29,7 +29,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "Mso\Mso.vcxitems", " EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso.UnitTests", "Mso.UnitTests\Mso.UnitTests.vcxproj", "{1958CEAA-FBE0-44E3-8A99-90AD85531FFE}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactWindowsCore", "ReactWindowsCore\ReactWindowsCore.vcxitems", "{11c084a3-a57c-4296-a679-cac17b603145}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactWindowsCore", "ReactWindowsCore\ReactWindowsCore.vcxitems", "{11C084A3-A57C-4296-A679-CAC17B603145}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactWindowsCore", "ReactWindowsCore\ReactWindowsCore.vcxproj", "{11C084A3-A57C-4296-A679-CAC17B603144}" EndProject @@ -118,6 +118,8 @@ EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution JSI\Shared\JSI.Shared.vcxitems*{0cc28589-39e4-4288-b162-97b959f8b843}*SharedItemsImports = 9 + ReactWindowsCore\ReactWindowsCore.vcxitems*{11c084a3-a57c-4296-a679-cac17b603144}*SharedItemsImports = 4 + ReactWindowsCore\ReactWindowsCore.vcxitems*{11c084a3-a57c-4296-a679-cac17b603145}*SharedItemsImports = 9 Mso\Mso.vcxitems*{1958ceaa-fbe0-44e3-8a99-90ad85531ffe}*SharedItemsImports = 4 Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9 Microsoft.ReactNative.SharedManaged\Microsoft.ReactNative.SharedManaged.projitems*{46d76f7a-8fd9-4a7d-8102-2857e5da6b84}*SharedItemsImports = 4 diff --git a/vnext/Microsoft.ReactNative/ABIViewManager.cpp b/vnext/Microsoft.ReactNative/ABIViewManager.cpp index 907d4365c4e..763972d0d16 100644 --- a/vnext/Microsoft.ReactNative/ABIViewManager.cpp +++ b/vnext/Microsoft.ReactNative/ABIViewManager.cpp @@ -27,7 +27,7 @@ ABIViewManager::ABIViewManager( m_viewManagerWithChildren{viewManager.try_as()}, m_name{to_string(viewManager.Name())} { if (m_viewManagerWithReactContext) { - m_viewManagerWithReactContext.ReactContext(winrt::make(Mso::Copy(reactContext))); + m_viewManagerWithReactContext.ReactContext(winrt::make(Mso::Copy(reactContext))); } if (m_viewManagerWithNativeProperties) { m_nativeProps = m_viewManagerWithNativeProperties.NativeProps(); diff --git a/vnext/Microsoft.ReactNative/IReactContext.cpp b/vnext/Microsoft.ReactNative/IReactContext.cpp index 2f884c41292..295293a601c 100644 --- a/vnext/Microsoft.ReactNative/IReactContext.cpp +++ b/vnext/Microsoft.ReactNative/IReactContext.cpp @@ -5,7 +5,7 @@ #include "IReactContext.h" #include "DynamicWriter.h" -namespace winrt::Microsoft::ReactNative { +namespace winrt::Microsoft::ReactNative::implementation { ReactContext::ReactContext(Mso::CntPtr &&context) noexcept : m_context{std::move(context)} {} @@ -13,6 +13,10 @@ IReactPropertyBag ReactContext::Properties() noexcept { return m_context->Properties(); } +IReactDispatcher ReactContext::UIDispatcher() noexcept { + return Properties().Get(ReactDispatcherHelper::UIDispatcherProperty()).try_as(); +} + void ReactContext::DispatchEvent( winrt::Windows::UI::Xaml::FrameworkElement const &view, hstring const &eventName, @@ -51,4 +55,4 @@ void ReactContext::EmitJSEvent( m_context->CallJSFunction(to_string(eventEmitterName), "emit", std::move(params)); } -} // namespace winrt::Microsoft::ReactNative +} // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/IReactContext.h b/vnext/Microsoft.ReactNative/IReactContext.h index 5770c2dfb1d..22222514fab 100644 --- a/vnext/Microsoft.ReactNative/IReactContext.h +++ b/vnext/Microsoft.ReactNative/IReactContext.h @@ -6,13 +6,14 @@ #include "ReactHost/React.h" #include "winrt/Microsoft.ReactNative.h" -namespace winrt::Microsoft::ReactNative { +namespace winrt::Microsoft::ReactNative::implementation { struct ReactContext : winrt::implements { ReactContext(Mso::CntPtr &&context) noexcept; public: // IReactContext IReactPropertyBag Properties() noexcept; + IReactDispatcher UIDispatcher() noexcept; void DispatchEvent( winrt::Windows::UI::Xaml::FrameworkElement const &view, hstring const &eventName, @@ -30,4 +31,4 @@ struct ReactContext : winrt::implements { Mso::CntPtr m_context; }; -} // namespace winrt::Microsoft::ReactNative +} // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/IReactContext.idl b/vnext/Microsoft.ReactNative/IReactContext.idl index 8ab131c9abf..43b684a1595 100644 --- a/vnext/Microsoft.ReactNative/IReactContext.idl +++ b/vnext/Microsoft.ReactNative/IReactContext.idl @@ -2,6 +2,7 @@ // Licensed under the MIT License. import "IJSValueWriter.idl"; +import "IReactDispatcher.idl"; import "IReactPropertyBag.idl"; namespace Microsoft.ReactNative { @@ -10,6 +11,9 @@ namespace Microsoft.ReactNative { interface IReactContext { IReactPropertyBag Properties { get; }; void DispatchEvent(Windows.UI.Xaml.FrameworkElement view, String eventName, JSValueArgWriter eventDataArgWriter); + // Get ReactDispatcherHelper::UIDispatcherProperty from the Properties property bag. + IReactDispatcher UIDispatcher { get; }; + void CallJSFunction(String moduleName, String methodName, JSValueArgWriter paramsArgWriter); void EmitJSEvent(String eventEmitterName, String eventName, JSValueArgWriter paramsArgWriter); } diff --git a/vnext/Microsoft.ReactNative/IReactDispatcher.cpp b/vnext/Microsoft.ReactNative/IReactDispatcher.cpp index 4c394e94825..5ff76866ecc 100644 --- a/vnext/Microsoft.ReactNative/IReactDispatcher.cpp +++ b/vnext/Microsoft.ReactNative/IReactDispatcher.cpp @@ -8,7 +8,7 @@ using namespace winrt; using namespace Windows::Foundation; -namespace winrt::Microsoft::ReactNative { +namespace winrt::Microsoft::ReactNative::implementation { ReactDispatcher::ReactDispatcher(Mso::DispatchQueue &&queue) noexcept : m_queue{std::move(queue)} {} @@ -20,25 +20,49 @@ void ReactDispatcher::Post(ReactDispatcherCallback const &callback) noexcept { return m_queue.Post([callback]() noexcept { callback(); }); } +/*static*/ IReactDispatcher ReactDispatcher::CreateSerialDispatcher() noexcept { + return make(Mso::DispatchQueue{}); +} + /*static*/ Mso::DispatchQueue ReactDispatcher::GetUIDispatchQueue(IReactPropertyBag const &properties) noexcept { return GetUIDispatcher(properties).as()->m_queue; } /*static*/ IReactDispatcher ReactDispatcher::UIThreadDispatcher() noexcept { - return make(Mso::DispatchQueue::MakeCurrentThreadUIQueue()); + static thread_local weak_ref *tlsWeakDispatcher{nullptr}; + IReactDispatcher dispatcher{nullptr}; + auto queue = Mso::DispatchQueue::GetCurrentUIThreadQueue(); + if (queue && queue.HasThreadAccess()) { + queue.InvokeElsePost([&queue, &dispatcher ]() noexcept { + // This code runs synchronously, but we want it to be run the queue context to + // access the queue local value where we store the weak_ref to the dispatcher. + // The queue local values are destroyed along with the queue. + // To access queue local value we temporary swap it with the thread local value. + // It must be a TLS value to ensure proper indexing of the queue local value entry. + auto tlsGuard{queue.LockLocalValue(&tlsWeakDispatcher)}; + dispatcher = tlsWeakDispatcher->get(); + if (!dispatcher) { + dispatcher = winrt::make(std::move(queue)); + *tlsWeakDispatcher = dispatcher; + } + }); + } + + return dispatcher; } -/*static*/ ReactPropertyId ReactDispatcher::UIDispatcherProperty() noexcept { - static ReactPropertyId uiThreadDispatcherProperty{L"ReactNative.Dispatcher", L"UIDispatcher"}; +/*static*/ IReactPropertyName ReactDispatcher::UIDispatcherProperty() noexcept { + static IReactPropertyName uiThreadDispatcherProperty{ReactPropertyBagHelper::GetName( + ReactPropertyBagHelper::GetNamespace(L"ReactNative.Dispatcher"), L"UIDispatcher")}; return uiThreadDispatcherProperty; } /*static*/ IReactDispatcher ReactDispatcher::GetUIDispatcher(IReactPropertyBag const &properties) noexcept { - return ReactPropertyBag{properties}.Get(UIDispatcherProperty()); + return properties.Get(UIDispatcherProperty()).try_as(); } /*static*/ void ReactDispatcher::SetUIThreadDispatcher(IReactPropertyBag const &properties) noexcept { - ReactPropertyBag{properties}.Set(UIDispatcherProperty(), UIThreadDispatcher()); + properties.Set(UIDispatcherProperty(), UIThreadDispatcher()); } -} // namespace winrt::Microsoft::ReactNative +} // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/IReactDispatcher.h b/vnext/Microsoft.ReactNative/IReactDispatcher.h index e71c0526012..99cdda6b11c 100644 --- a/vnext/Microsoft.ReactNative/IReactDispatcher.h +++ b/vnext/Microsoft.ReactNative/IReactDispatcher.h @@ -3,23 +3,24 @@ #pragma once #include "ReactDispatcherHelper.g.h" -#include #include #include -namespace winrt::Microsoft::ReactNative { +namespace winrt::Microsoft::ReactNative::implementation { -struct ReactDispatcher : implements { +struct ReactDispatcher : implements> { ReactDispatcher() = default; ReactDispatcher(Mso::DispatchQueue &&queue) noexcept; bool HasThreadAccess() noexcept; void Post(ReactDispatcherCallback const &callback) noexcept; + static IReactDispatcher CreateSerialDispatcher() noexcept; + static Mso::DispatchQueue GetUIDispatchQueue(IReactPropertyBag const &properties) noexcept; static IReactDispatcher UIThreadDispatcher() noexcept; - static ReactPropertyId UIDispatcherProperty() noexcept; + static IReactPropertyName UIDispatcherProperty() noexcept; static IReactDispatcher GetUIDispatcher(IReactPropertyBag const &properties) noexcept; static void SetUIThreadDispatcher(IReactPropertyBag const &properties) noexcept; @@ -27,19 +28,19 @@ struct ReactDispatcher : implements { Mso::DispatchQueue m_queue; }; -} // namespace winrt::Microsoft::ReactNative - -namespace winrt::Microsoft::ReactNative::implementation { - struct ReactDispatcherHelper { ReactDispatcherHelper() = default; + static IReactDispatcher CreateSerialDispatcher() noexcept { + return ReactDispatcher::CreateSerialDispatcher(); + } + static IReactDispatcher UIThreadDispatcher() noexcept { return ReactDispatcher::UIThreadDispatcher(); } static IReactPropertyName UIDispatcherProperty() noexcept { - return ReactDispatcher::UIDispatcherProperty().Handle(); + return ReactDispatcher::UIDispatcherProperty(); } }; diff --git a/vnext/Microsoft.ReactNative/IReactDispatcher.idl b/vnext/Microsoft.ReactNative/IReactDispatcher.idl index e5f3d9b6d5f..d9ef4576c81 100644 --- a/vnext/Microsoft.ReactNative/IReactDispatcher.idl +++ b/vnext/Microsoft.ReactNative/IReactDispatcher.idl @@ -23,10 +23,13 @@ namespace Microsoft.ReactNative { [webhosthidden] static runtimeclass ReactDispatcherHelper { + // Creates a new serial dispatcher that uses thread pool to run tasks. + static IReactDispatcher CreateSerialDispatcher(); + // Get or create IReactDispatcher for the current UI thread. static IReactDispatcher UIThreadDispatcher{ get; }; // Get name of the UIDispatcher property for the IReactPropertyBag. - static IReactPropertyName UIDispatcherProperty(); + static IReactPropertyName UIDispatcherProperty { get; }; } } // namespace ReactNative diff --git a/vnext/Microsoft.ReactNative/NativeModulesProvider.cpp b/vnext/Microsoft.ReactNative/NativeModulesProvider.cpp index 25c9199bfb2..566891c6cd8 100644 --- a/vnext/Microsoft.ReactNative/NativeModulesProvider.cpp +++ b/vnext/Microsoft.ReactNative/NativeModulesProvider.cpp @@ -34,7 +34,7 @@ std::vector NativeModulesProvider::Get m_modulesWorkerQueue = react::uwp::MakeSerialQueueThread(); } - auto winrtReactContext = winrt::make(Mso::Copy(reactContext)); + auto winrtReactContext = winrt::make(Mso::Copy(reactContext)); for (auto &entry : m_moduleProviders) { modules.emplace_back( diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index a700f383c2c..aa3b22f86b9 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -387,7 +387,12 @@ void ReactInstanceWin::OnReactInstanceLoaded(const Mso::ErrorCode &errorCode) no if (auto strongThis = weakThis.GetStrongPtr()) { if (!strongThis->m_isLoaded) { strongThis->m_isLoaded = true; - strongThis->m_state = ReactInstanceState::Loaded; + if (!errorCode) { + strongThis->m_state = ReactInstanceState::Loaded; + } else { + strongThis->m_state = ReactInstanceState::HasError; + } + if (auto onLoaded = strongThis->m_options.OnInstanceLoaded.Get()) { onLoaded->Invoke(*strongThis, errorCode); } @@ -456,7 +461,8 @@ void ReactInstanceWin::InitNativeMessageThread() noexcept { void ReactInstanceWin::InitUIMessageThread() noexcept { // Native queue was already given us in constructor. - m_uiQueue = winrt::Microsoft::ReactNative::ReactDispatcher::GetUIDispatchQueue(m_options.Properties); + m_uiQueue = winrt::Microsoft::ReactNative::implementation::ReactDispatcher::GetUIDispatchQueue(m_options.Properties); + VerifyElseCrashSz(m_uiQueue, "No UI Dispatcher provided"); m_uiMessageThread.Exchange( std::make_shared(m_uiQueue, Mso::MakeWeakMemberFunctor(this, &ReactInstanceWin::OnError))); diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettings.cpp b/vnext/Microsoft.ReactNative/ReactInstanceSettings.cpp index f1bc040699b..98d58b8c98b 100644 --- a/vnext/Microsoft.ReactNative/ReactInstanceSettings.cpp +++ b/vnext/Microsoft.ReactNative/ReactInstanceSettings.cpp @@ -7,4 +7,19 @@ #include "ReactInstanceSettings.g.cpp" #endif -namespace winrt::Microsoft::ReactNative::implementation {} // namespace winrt::Microsoft::ReactNative::implementation +namespace winrt::Microsoft::ReactNative::implementation { + +ReactInstanceSettings::ReactInstanceSettings() noexcept { + // Use current thread dispatcher as a default UI dispatcher. + m_properties.Set(ReactDispatcherHelper::UIDispatcherProperty(), ReactDispatcherHelper::UIThreadDispatcher()); +} + +IReactDispatcher ReactInstanceSettings::UIDispatcher() noexcept { + return m_properties.Get(ReactDispatcherHelper::UIDispatcherProperty()).try_as(); +} + +void ReactInstanceSettings::UIDispatcher(IReactDispatcher const &value) noexcept { + m_properties.Set(ReactDispatcherHelper::UIDispatcherProperty(), value); +} + +} // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettings.h b/vnext/Microsoft.ReactNative/ReactInstanceSettings.h index 1e828273511..ca7373d6e37 100644 --- a/vnext/Microsoft.ReactNative/ReactInstanceSettings.h +++ b/vnext/Microsoft.ReactNative/ReactInstanceSettings.h @@ -22,7 +22,7 @@ namespace winrt::Microsoft::ReactNative::implementation { struct ReactInstanceSettings : ReactInstanceSettingsT { - ReactInstanceSettings() = default; + ReactInstanceSettings() noexcept; IReactPropertyBag Properties() noexcept; @@ -83,6 +83,9 @@ struct ReactInstanceSettings : ReactInstanceSettingsT { IRedBoxHandler RedBoxHandler() noexcept; void RedBoxHandler(IRedBoxHandler const &value) noexcept; + IReactDispatcher UIDispatcher() noexcept; + void UIDispatcher(IReactDispatcher const &value) noexcept; + private: IReactPropertyBag m_properties{ReactPropertyBagHelper::CreatePropertyBag()}; hstring m_mainComponentName{}; diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl b/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl index fff4532c2d4..c96c3a1ef37 100644 --- a/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl +++ b/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import "RedBoxHandler.idl"; +import "IReactDispatcher.idl"; import "IReactPropertyBag.idl"; +import "RedBoxHandler.idl"; namespace Microsoft.ReactNative { @@ -31,5 +32,6 @@ namespace Microsoft.ReactNative { String BundleRootPath { get; set; }; UInt16 DebuggerPort { get; set; }; IRedBoxHandler RedBoxHandler { get; set; }; + IReactDispatcher UIDispatcher { get; set; }; } } diff --git a/vnext/Microsoft.ReactNative/RedBox.cpp b/vnext/Microsoft.ReactNative/RedBox.cpp index 6709f9b6c35..934f9bd0818 100644 --- a/vnext/Microsoft.ReactNative/RedBox.cpp +++ b/vnext/Microsoft.ReactNative/RedBox.cpp @@ -55,10 +55,12 @@ struct RedBox : public std::enable_shared_from_this { } void WindowSizeChanged(winrt::Windows::UI::Core::WindowSizeChangedEventArgs const &args) noexcept { - m_redboxContent.MaxHeight(args.Size().Height); - m_redboxContent.Height(args.Size().Height); - m_redboxContent.MaxWidth(args.Size().Width); - m_redboxContent.Width(args.Size().Width); + if (m_redboxContent) { + m_redboxContent.MaxHeight(args.Size().Height); + m_redboxContent.Height(args.Size().Height); + m_redboxContent.MaxWidth(args.Size().Width); + m_redboxContent.Width(args.Size().Width); + } } void ShowNewJSError() noexcept { @@ -147,6 +149,7 @@ struct RedBox : public std::enable_shared_from_this { m_reloadButton.Click(m_tokenReload); xaml::Window::Current().SizeChanged(m_tokenSizeChanged); m_popup.Closed(m_tokenClosed); + m_redboxContent = nullptr; m_onClosedCallback(GetId()); } diff --git a/vnext/Microsoft.ReactNative/Threading/MessageQueueThreadFactory.cpp b/vnext/Microsoft.ReactNative/Threading/MessageQueueThreadFactory.cpp index 1dfd9e4f3c5..803369b3f7f 100644 --- a/vnext/Microsoft.ReactNative/Threading/MessageQueueThreadFactory.cpp +++ b/vnext/Microsoft.ReactNative/Threading/MessageQueueThreadFactory.cpp @@ -12,8 +12,10 @@ std::shared_ptr MakeJSQueueThread() noexcep } std::shared_ptr MakeUIQueueThread() noexcept { - return std::make_shared( - Mso::DispatchQueue::MakeCurrentThreadUIQueue(), nullptr, nullptr); + Mso::DispatchQueue queue = Mso::DispatchQueue::GetCurrentUIThreadQueue(); + std::shared_ptr messageThread = + queue ? std::make_shared(queue, nullptr, nullptr) : nullptr; + return messageThread; } std::shared_ptr MakeSerialQueueThread() noexcept { diff --git a/vnext/Microsoft.ReactNative/Views/ReactRootControl.cpp b/vnext/Microsoft.ReactNative/Views/ReactRootControl.cpp index 4ffa80aafc9..0e5451e13be 100644 --- a/vnext/Microsoft.ReactNative/Views/ReactRootControl.cpp +++ b/vnext/Microsoft.ReactNative/Views/ReactRootControl.cpp @@ -49,7 +49,8 @@ namespace react::uwp { //=========================================================================== ReactRootControl::ReactRootControl(XamlView const &rootView) noexcept - : m_weakRootView{rootView}, m_uiQueue(Mso::DispatchQueue::MakeCurrentThreadUIQueue()) { + : m_weakRootView{rootView}, m_uiQueue(Mso::DispatchQueue::GetCurrentUIThreadQueue()) { + VerifyElseCrashSz(m_uiQueue, "Cannot get UI dispatch queue for the current thread"); PrepareXamlRootView(rootView); } diff --git a/vnext/Mso/dispatchQueue/dispatchQueue.h b/vnext/Mso/dispatchQueue/dispatchQueue.h index 50e2ecbe1e0..c37efbb1b14 100644 --- a/vnext/Mso/dispatchQueue/dispatchQueue.h +++ b/vnext/Mso/dispatchQueue/dispatchQueue.h @@ -48,7 +48,7 @@ enum class PendingTaskAction { }; //! Callback type to handle queue local values -using SwapDispatchLocalValueCallback = void (*)(void **localValue, void *tlsValue) noexcept; +using SwapDispatchLocalValueCallback = void (*)(void **localValue, void **tlsValue) noexcept; //! Gets a pointer to the state storage. This is a 'back-door' for advanced scenarios. Use with caution! template @@ -58,7 +58,7 @@ auto GetRawState(TObject &&obj) noexcept; //! It creates new value if localValue is null. //! It destroys local value if tlsValue is null. template -void SwapDispatchLocalValue(void **localValue, void *tlsValue) noexcept; +void SwapDispatchLocalValue(void **localValue, void **tlsValue) noexcept; template Mso::VoidFunctor MakeDispatchTask(TInvoke &&invoke, TOnCancel &&onCancel) noexcept; @@ -69,7 +69,7 @@ Mso::VoidFunctor MakeDispatchCleanupTask(TInvoke &&invoke) noexcept; //! RAII class to unlock the queue local value by swapping it back with TLS variable. struct DispatchLocalValueGuard { //! Create new DispatchLocalValueGuard instance and swap TLS value with the queue local value. - DispatchLocalValueGuard(IDispatchQueueService *queue, void *tlsValue) noexcept; + DispatchLocalValueGuard(IDispatchQueueService *queue, void **tlsValue) noexcept; //! Swap back the TLS value with the queue local value. It must run on the same thread as the constructor. ~DispatchLocalValueGuard() noexcept; @@ -82,7 +82,7 @@ struct DispatchLocalValueGuard { private: IDispatchQueueService *m_queue; - void *m_tlsValue; + void **m_tlsValue; }; //! Serial or concurrent dispatch queue main API. @@ -119,8 +119,9 @@ struct DispatchQueue { //! Create new looper DispatchQueue on top of new std::thread. It owns the thread until shutdown. static DispatchQueue MakeLooperQueue() noexcept; - //! Create new UI DispatchQueue for the current UI thread. - static DispatchQueue MakeCurrentThreadUIQueue() noexcept; + //! Get a dispatch queue for the current UI thread. The result is null if the UI thread has no system UI thread + //! dispatcher. + static DispatchQueue GetCurrentUIThreadQueue() noexcept; //! Create a concurrent queue on top of platform specific thread pool that uses up to maxThreads threads. //! If maxThreads is zero, then it creates a concurrent queue that has a predefined limit on concurrently submitted @@ -174,7 +175,7 @@ struct DispatchQueue { //! The returned DispatchLocalValueGuard returns true when casted to bool in case of success. It can be used inside of //! an 'if' statement. template - DispatchLocalValueGuard LockLocalValue(TValue *tlsValue) const noexcept; + DispatchLocalValueGuard LockLocalValue(TValue **tlsValue) const noexcept; //! Suspend asynchronous task invocation and return a guard that resumes it in its destructor. DispatchSuspendGuard Suspend() const noexcept; @@ -361,10 +362,10 @@ struct IDispatchQueueService : IUnknown { //! Unlocking the QLV swaps it back with the TLS variable. //! On the first call the variable will be created by swapLocalValue. When queue is shutdown the variable is destroyed //! by swapLocalValue. The TLS variable address corresponding to the QLV is used to define a key for storing QLV. - virtual bool TryLockQueueLocalValue(SwapDispatchLocalValueCallback swapLocalValue, void *tlsValue) noexcept = 0; + virtual bool TryLockQueueLocalValue(SwapDispatchLocalValueCallback swapLocalValue, void **tlsValue) noexcept = 0; //! Unlock the queue local value by swapping it back with the TLS value. - virtual void UnlockLocalValue(void *tlsValue) noexcept = 0; + virtual void UnlockLocalValue(void **tlsValue) noexcept = 0; //! Suspend task invocation and increment internal suspend count by one. //! Note that we do not expose IsSuspeneded() method because the Suspend/Resume @@ -418,8 +419,9 @@ struct IDispatchQueueStatic : IUnknown { //! Create new looper DispatchQueue on top of new std::thread. It owns the thread until shutdown. virtual DispatchQueue MakeLooperQueue() noexcept = 0; - //! Creates new UI DispatchQueue for the current UI thread. - virtual DispatchQueue MakeCurrentThreadUIQueue() noexcept = 0; + //! Get a dispatch queue for the current UI thread. The result is null if the UI thread has no system UI thread + //! dispatcher. + virtual DispatchQueue GetCurrentUIThreadQueue() noexcept = 0; //! Create a concurrent queue on top of platform specific thread pool that uses up to maxThreads threads. //! If maxThreads is zero, then it creates a concurrent queue that has a predefined limit on concurrently submitted @@ -474,17 +476,18 @@ inline auto GetRawState(TObject &&obj) noexcept { } template -void SwapQueueLocalValue(void **localValue, void *tlsValue) noexcept { +void SwapDispatchLocalValue(void **localValue, void **tlsValue) noexcept { if (!tlsValue) { // Delete the queue local value when tlsValue is null. if (*localValue) { delete static_cast(*localValue); *localValue = nullptr; } - } else if (!*localValue) { // Initialize queue local value on the first call + return; + } else if (!*tlsValue && !*localValue) { // Initialize queue local value on the first call *localValue = new TValue{}; } using std::swap; - swap(*static_cast(*localValue), *static_cast(tlsValue)); + swap(*localValue, *tlsValue); } template @@ -504,7 +507,7 @@ inline Mso::VoidFunctor MakeDispatchCleanupTask(TInvoke &&invoke) noexcept { // DispatchLocalValueGuard inline implementation //============================================================================= -inline DispatchLocalValueGuard::DispatchLocalValueGuard(IDispatchQueueService *queue, void *tlsValue) noexcept +inline DispatchLocalValueGuard::DispatchLocalValueGuard(IDispatchQueueService *queue, void **tlsValue) noexcept : m_queue{queue}, m_tlsValue{tlsValue} {} inline DispatchLocalValueGuard::~DispatchLocalValueGuard() noexcept { @@ -546,8 +549,8 @@ inline /*static*/ DispatchQueue DispatchQueue::MakeLooperQueue() noexcept { return IDispatchQueueStatic::Instance()->MakeLooperQueue(); } -inline /*static*/ DispatchQueue DispatchQueue::MakeCurrentThreadUIQueue() noexcept { - return IDispatchQueueStatic::Instance()->MakeCurrentThreadUIQueue(); +inline /*static*/ DispatchQueue DispatchQueue::GetCurrentUIThreadQueue() noexcept { + return IDispatchQueueStatic::Instance()->GetCurrentUIThreadQueue(); } inline /*static*/ DispatchQueue DispatchQueue::MakeConcurrentQueue(uint32_t maxThreads) noexcept { @@ -596,9 +599,9 @@ inline DispatchTaskBatch DispatchQueue::StartTaskBatching() const noexcept { } template -inline DispatchLocalValueGuard DispatchQueue::LockLocalValue(TValue *tlsValue) const noexcept { - if (m_state->TryLockQueueLocalValue(SwapDispatchLocalValue, tlsValue)) { - return DispatchLocalValueGuard{m_state.Get(), tlsValue}; +inline DispatchLocalValueGuard DispatchQueue::LockLocalValue(TValue **tlsValue) const noexcept { + if (m_state->TryLockQueueLocalValue(SwapDispatchLocalValue, (void **)tlsValue)) { + return DispatchLocalValueGuard{m_state.Get(), (void **)tlsValue}; } else { return DispatchLocalValueGuard{nullptr, nullptr}; } diff --git a/vnext/Mso/src/dispatchQueue/queueService.cpp b/vnext/Mso/src/dispatchQueue/queueService.cpp index d30f444bdc0..b31b7f5de2e 100644 --- a/vnext/Mso/src/dispatchQueue/queueService.cpp +++ b/vnext/Mso/src/dispatchQueue/queueService.cpp @@ -122,17 +122,17 @@ bool QueueService::HasTaskBatching() noexcept { return m_taskBatches.find(std::this_thread::get_id()) != m_taskBatches.end(); } -bool QueueService::TryLockQueueLocalValue(SwapDispatchLocalValueCallback swapLocalValue, void *tlsValue) noexcept { - return TrySwapLocalValue(swapLocalValue, tlsValue, LocalValueSwapAction::Unlock); +bool QueueService::TryLockQueueLocalValue(SwapDispatchLocalValueCallback swapLocalValue, void **tlsValue) noexcept { + return TrySwapLocalValue(swapLocalValue, tlsValue, LocalValueSwapAction::Lock); } -void QueueService::UnlockLocalValue(void *tlsValue) noexcept { +void QueueService::UnlockLocalValue(void **tlsValue) noexcept { TrySwapLocalValue(nullptr, tlsValue, LocalValueSwapAction::Unlock); } bool QueueService::TrySwapLocalValue( SwapDispatchLocalValueCallback swapLocalValue, - void *tlsValue, + void **tlsValue, LocalValueSwapAction action) noexcept { std::lock_guard lock{m_mutex}; @@ -140,7 +140,7 @@ bool QueueService::TrySwapLocalValue( // layout between threads. We use address delta between the provided TLS value and // our internal localValueAnchor. It must be the same for a TLS value between threads. static thread_local uint8_t localValueAnchor{0}; - ptrdiff_t key = static_cast(tlsValue) - &localValueAnchor; + ptrdiff_t key = static_cast(static_cast(tlsValue)) - &localValueAnchor; auto [it, added] = m_localValues.try_emplace(key, swapLocalValue); return it->second.TrySwapLocalValue(tlsValue, action); } @@ -230,7 +230,7 @@ QueueLocalValueEntry::~QueueLocalValueEntry() noexcept { m_swapLocalValue(&m_data, nullptr); } -bool QueueLocalValueEntry::TrySwapLocalValue(void *tlsValue, LocalValueSwapAction action) noexcept { +bool QueueLocalValueEntry::TrySwapLocalValue(void **tlsValue, LocalValueSwapAction action) noexcept { if (action == LocalValueSwapAction::Lock && !m_isLocked) { m_isLocked = true; } else if (action == LocalValueSwapAction::Unlock && m_isLocked) { @@ -277,10 +277,6 @@ DispatchQueue DispatchQueueStatic::MakeLooperQueue() noexcept { return Mso::Make(MakeLooperScheduler()); } -DispatchQueue DispatchQueueStatic::MakeCurrentThreadUIQueue() noexcept { - return Mso::Make(MakeCurrentThreadUIScheduler()); -} - DispatchQueue DispatchQueueStatic::MakeConcurrentQueue(uint32_t maxThreads) noexcept { return Mso::Make(MakeThreadPoolScheduler(maxThreads)); } diff --git a/vnext/Mso/src/dispatchQueue/queueService.h b/vnext/Mso/src/dispatchQueue/queueService.h index a80f72a669b..0a609d3ed9b 100644 --- a/vnext/Mso/src/dispatchQueue/queueService.h +++ b/vnext/Mso/src/dispatchQueue/queueService.h @@ -40,8 +40,8 @@ struct QueueService : Mso::UnknownObject { static DispatchQueueStatic *Instance() noexcept; static Mso::CntPtr MakeLooperScheduler() noexcept; - static Mso::CntPtr MakeCurrentThreadUIScheduler() noexcept; static Mso::CntPtr MakeThreadPoolScheduler(uint32_t maxThreads) noexcept; public: // IDispatchQueueStatic @@ -90,7 +89,7 @@ struct DispatchQueueStatic : Mso::UnknownObject &&scheduler) noexcept override; }; diff --git a/vnext/Mso/src/dispatchQueue/uiScheduler_winrt.cpp b/vnext/Mso/src/dispatchQueue/uiScheduler_winrt.cpp index 70981a780a1..b548b486d5f 100644 --- a/vnext/Mso/src/dispatchQueue/uiScheduler_winrt.cpp +++ b/vnext/Mso/src/dispatchQueue/uiScheduler_winrt.cpp @@ -5,13 +5,61 @@ #include "object/refCountedObject.h" #include "queueService.h" #include "taskQueue.h" +#include "winrt/Windows.Foundation.h" #include "winrt/Windows.System.h" using namespace winrt; +using namespace Windows::Foundation; using namespace Windows::System; namespace Mso { +namespace { + +// TODO: consider to move it into its own liblet +template +struct ThreadSafeMap { + ThreadSafeMap(std::recursive_mutex &mutex) noexcept : m_mutex{mutex} {} + + std::optional Get(TKey const &key) noexcept { + std::scoped_lock lock{m_mutex}; + auto it = m_map.find(key); + if (it != m_map.end()) { + return std::optional{it->second}; + } else { + return std::optional{}; + } + } + + void Set(TKey const &key, TValue &&value) noexcept { + TValue oldValue; // to destroy outside of lock + { + std::scoped_lock lock{m_mutex}; + auto &valueRef = m_map[key]; + oldValue = std::move(valueRef); + valueRef = std::move(value); + } + } + + void Remove(TKey const &key) noexcept { + TValue value; // to destroy outside of lock + { + std::scoped_lock lock{m_mutex}; + auto it = m_map.find(key); + if (it != m_map.end()) { + value = std::move(it->second); + m_map.erase(it); + } + } + } + + private: + std::recursive_mutex &m_mutex; + std::map> m_map; +}; + +} // namespace + struct UISchedulerWinRT; //! TaskDispatcherHandler is a DispatcherQueueHandler delegate that we pass to DispatcherQueue. @@ -37,6 +85,10 @@ struct UISchedulerWinRT : Mso::UnknownObject &queue, DispatchTask &task) noexcept; + static DispatchQueue GetOrCreateUIThreadQueue() noexcept; + using DispatchQueueRegistry = ThreadSafeMap>; + static DispatchQueueRegistry &GetDispatchQueueRegistry() noexcept; + public: // IDispatchQueueScheduler void IntializeScheduler(Mso::WeakPtr &&queue) noexcept override; bool HasThreadAccess() noexcept override; @@ -46,6 +98,8 @@ struct UISchedulerWinRT : Mso::UnknownObject> weakQueue = GetDispatchQueueRegistry().Get(threadId); + DispatchQueue queue{weakQueue.value_or(nullptr).GetStrongPtr()}; + if (queue) { + return queue; + } + + auto dispatcher = DispatcherQueue::GetForCurrentThread(); + if (!dispatcher) { + return queue; + } + + queue = + DispatchQueue(Mso::Make(Mso::Make(std::move(dispatcher)))); + GetDispatchQueueRegistry().Set(threadId, Mso::WeakPtr{*GetRawState(queue)}); + return queue; +} + +/*static*/ UISchedulerWinRT::DispatchQueueRegistry &UISchedulerWinRT::GetDispatchQueueRegistry() noexcept { + static std::recursive_mutex mutex; + static DispatchQueueRegistry registry{mutex}; + return registry; +} + //============================================================================= // UISchedulerWinRT::CleanupContext implementation //============================================================================= @@ -248,8 +334,8 @@ void UISchedulerWinRT::CleanupContext::CheckTermination() noexcept { // DispatchQueueStatic::MakeCurrentThreadUIScheduler implementation //============================================================================= -/*static*/ Mso::CntPtr DispatchQueueStatic::MakeCurrentThreadUIScheduler() noexcept { - return Mso::Make(DispatcherQueue::GetForCurrentThread()); +DispatchQueue DispatchQueueStatic::GetCurrentUIThreadQueue() noexcept { + return UISchedulerWinRT::GetOrCreateUIThreadQueue(); } } // namespace Mso diff --git a/vnext/ReactUWP/Base/UwpReactInstance.cpp b/vnext/ReactUWP/Base/UwpReactInstance.cpp index 448262ba87c..678d26bbb89 100644 --- a/vnext/ReactUWP/Base/UwpReactInstance.cpp +++ b/vnext/ReactUWP/Base/UwpReactInstance.cpp @@ -116,7 +116,7 @@ void UwpReactInstance::Start(const std::shared_ptr &spThis, cons std::shared_ptr appTheme = std::make_shared(spThis, m_defaultNativeThread); I18nHelper::Instance().setInfo(I18nModule::GetI18nInfo()); - auto appearanceListener = Mso::Make(spThis, Mso::DispatchQueue::MakeCurrentThreadUIQueue()); + auto appearanceListener = Mso::Make(spThis, Mso::DispatchQueue::GetCurrentUIThreadQueue()); // TODO: Figure out threading. What thread should this really be on? m_initThread = std::make_unique(); diff --git a/vnext/ReactWindows-Desktop.sln b/vnext/ReactWindows-Desktop.sln index 4c4f60f46fd..6e3ddae5d0a 100644 --- a/vnext/ReactWindows-Desktop.sln +++ b/vnext/ReactWindows-Desktop.sln @@ -9,7 +9,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactCommon", "ReactCommon\ {A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {A990658C-CE31-4BCC-976F-0FC6B1AF693D} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactWindowsCore", "ReactWindowsCore\ReactWindowsCore.vcxitems", "{11c084a3-a57c-4296-a679-cac17b603145}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactWindowsCore", "ReactWindowsCore\ReactWindowsCore.vcxitems", "{11C084A3-A57C-4296-A679-CAC17B603145}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactWindowsCore-Desktop", "ReactWindowsCore\ReactWindowsCore-Desktop.vcxproj", "{11C084A3-A57C-4296-A679-CAC17B603144}" ProjectSection(ProjectDependencies) = postProject @@ -123,6 +123,7 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution JSI\Shared\JSI.Shared.vcxitems*{0cc28589-39e4-4288-b162-97b959f8b843}*SharedItemsImports = 9 ReactWindowsCore\ReactWindowsCore.vcxitems*{11c084a3-a57c-4296-a679-cac17b603144}*SharedItemsImports = 4 + ReactWindowsCore\ReactWindowsCore.vcxitems*{11c084a3-a57c-4296-a679-cac17b603145}*SharedItemsImports = 9 JSI\Shared\JSI.Shared.vcxitems*{17dd1b17-3094-40dd-9373-ac2497932eca}*SharedItemsImports = 4 Mso\Mso.vcxitems*{1958ceaa-fbe0-44e3-8a99-90ad85531ffe}*SharedItemsImports = 4 Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9