Skip to content

Commit

Permalink
Enable custom timer factory to be provided (#12856)
Browse files Browse the repository at this point in the history
* Enable custom timer factory to be provided

* Change files
  • Loading branch information
acoates-ms committed Mar 21, 2024
1 parent c2a2bd6 commit 34c5cd8
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Enable custom timer factory to be provided",
"packageName": "react-native-windows",
"email": "30809111+acoates-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
4 changes: 4 additions & 0 deletions vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,10 @@
<DependentUpon>ReactNativeHost.idl</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="Timer.h">
<DependentUpon>Timer.idl</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="Utils\AccessibilityUtils.h" />
<ClInclude Include="Utils\Helpers.h" />
<ClInclude Include="Utils\KeyboardUtils.h" />
Expand Down
16 changes: 9 additions & 7 deletions vnext/Microsoft.ReactNative/Modules/Timing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ bool TimerQueue::IsEmpty() {
}

std::unique_ptr<TimerRegistry> TimerRegistry::CreateTimerRegistry(
const winrt::Microsoft::ReactNative::IReactDispatcher &uiDispatcher) noexcept {
const winrt::Microsoft::ReactNative::IReactPropertyBag &properties) noexcept {
auto registry = std::make_unique<TimerRegistry>();
registry->m_timingModule = std::make_shared<Timing>();
registry->m_timingModule->InitializeBridgeless(registry.get(), uiDispatcher);
registry->m_timingModule->InitializeBridgeless(registry.get(), properties);
return registry;
}

Expand Down Expand Up @@ -123,16 +123,19 @@ void TimerRegistry::setTimerManager(std::weak_ptr<facebook::react::TimerManager>

void Timing::Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept {
m_context = reactContext;
m_properties = reactContext.Properties().Handle();
m_usePostForRendering = !xaml::TryGetCurrentApplication();
m_uiDispatcher = m_context.UIDispatcher().Handle();
}

void Timing::InitializeBridgeless(
TimerRegistry *timerRegistry,
const winrt::Microsoft::ReactNative::IReactDispatcher &uiDispatcher) noexcept {
const winrt::Microsoft::ReactNative::IReactPropertyBag &properties) noexcept {
m_timerRegistry = timerRegistry;
m_properties = properties;
m_usePostForRendering = !xaml::TryGetCurrentApplication();
m_uiDispatcher = {uiDispatcher};
m_uiDispatcher = {properties.Get(winrt::Microsoft::ReactNative::ReactDispatcherHelper::UIDispatcherProperty())
.try_as<winrt::Microsoft::ReactNative::IReactDispatcher>()};
}

void Timing::DetachBridgeless() {
Expand Down Expand Up @@ -183,10 +186,9 @@ void Timing::OnTick() {
}
}

winrt::dispatching::DispatcherQueueTimer Timing::EnsureDispatcherTimer() {
winrt::Microsoft::ReactNative::ITimer Timing::EnsureDispatcherTimer() {
if (!m_dispatcherQueueTimer) {
const auto queue = winrt::dispatching::DispatcherQueue::GetForCurrentThread();
m_dispatcherQueueTimer = queue.CreateTimer();
m_dispatcherQueueTimer = winrt::Microsoft::ReactNative::Timer::Create(m_properties);
m_dispatcherQueueTimer.Tick([wkThis = std::weak_ptr(this->shared_from_this())](auto &&...) {
if (auto pThis = wkThis.lock()) {
pThis->OnTick();
Expand Down
9 changes: 5 additions & 4 deletions vnext/Microsoft.ReactNative/Modules/Timing.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class TimerQueue {

struct TimerRegistry : public facebook::react::PlatformTimerRegistry {
static std::unique_ptr<TimerRegistry> CreateTimerRegistry(
const winrt::Microsoft::ReactNative::IReactDispatcher &uiDispatcher) noexcept;
const winrt::Microsoft::ReactNative::IReactPropertyBag &properties) noexcept;

~TimerRegistry();

Expand Down Expand Up @@ -86,7 +86,7 @@ struct Timing : public std::enable_shared_from_this<Timing> {

void InitializeBridgeless(
TimerRegistry *timerRegistry,
const winrt::Microsoft::ReactNative::IReactDispatcher &uiDispatcher) noexcept;
const winrt::Microsoft::ReactNative::IReactPropertyBag &properties) noexcept;
void DetachBridgeless();

REACT_METHOD(createTimer)
Expand All @@ -101,17 +101,18 @@ struct Timing : public std::enable_shared_from_this<Timing> {
void createTimerOnQueue(uint32_t id, double duration, double jsSchedulingTime, bool repeat) noexcept;
void deleteTimerOnQueue(uint32_t id) noexcept;
void OnTick();
winrt::dispatching::DispatcherQueueTimer EnsureDispatcherTimer();
winrt::Microsoft::ReactNative::ITimer EnsureDispatcherTimer();
void StartRendering();
void PostRenderFrame() noexcept;
void StartDispatcherTimer();
void StopTicks();

winrt::Microsoft::ReactNative::ReactContext m_context; // !bridgeless
TimerRegistry *m_timerRegistry{nullptr}; // bridgeless
winrt::Microsoft::ReactNative::IReactPropertyBag m_properties{nullptr};
TimerQueue m_timerQueue;
xaml::Media::CompositionTarget::Rendering_revoker m_rendering;
winrt::dispatching::DispatcherQueueTimer m_dispatcherQueueTimer{nullptr};
winrt::Microsoft::ReactNative::ITimer m_dispatcherQueueTimer{nullptr};
winrt::weak_ref<winrt::Microsoft::ReactNative::IReactDispatcher> m_uiDispatcher;
bool m_usingRendering{false};
bool m_usePostForRendering{false};
Expand Down
15 changes: 15 additions & 0 deletions vnext/Microsoft.ReactNative/ReactCoreInjection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,21 @@ void ReactCoreInjection::SetTopLevelWindowId(const IReactPropertyBag &properties
ReactPropertyBag(properties).Set(TopLevelWindowIdPropertyId(), windowId);
}

static const ReactPropertyId<TimerFactory> &TimerFactoryPropertyId() noexcept {
static const ReactPropertyId<TimerFactory> prop{L"ReactNative.Injection", L"TimerFactory"};
return prop;
}

void ReactCoreInjection::SetTimerFactory(
const IReactPropertyBag &properties,
const TimerFactory &timerFactory) noexcept {
ReactPropertyBag(properties).Set(TimerFactoryPropertyId(), timerFactory);
}

TimerFactory ReactCoreInjection::GetTimerFactory(const IReactPropertyBag &properties) noexcept {
return ReactPropertyBag(properties).Get(TimerFactoryPropertyId()).value_or(nullptr);
}

ReactViewHost::ReactViewHost(
const ReactNative::ReactNativeHost &host,
Mso::React::IReactViewHost &viewHost,
Expand Down
4 changes: 4 additions & 0 deletions vnext/Microsoft.ReactNative/ReactCoreInjection.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ struct ReactCoreInjection : ReactCoreInjectionT<ReactCoreInjection> {

static uint64_t GetTopLevelWindowId(const IReactPropertyBag &properties) noexcept;
static void SetTopLevelWindowId(const IReactPropertyBag &properties, uint64_t windowId) noexcept;

static ITimer CreateTimer(const IReactPropertyBag &properties);
static TimerFactory GetTimerFactory(const IReactPropertyBag &properties) noexcept;
static void SetTimerFactory(const IReactPropertyBag &properties, const TimerFactory &timerFactory) noexcept;
};

struct ReactViewHost : public winrt::implements<ReactViewHost, IReactViewHost> {
Expand Down
8 changes: 8 additions & 0 deletions vnext/Microsoft.ReactNative/ReactCoreInjection.idl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import "IReactContext.idl";
import "ReactNativeHost.idl";
import "IJSValueReader.idl";
import "Timer.idl";

#include "DocString.h"

Expand All @@ -27,6 +28,10 @@ DOC_STRING("Settings per each IReactViewHost associated with an IReactHost insta
};
};

[experimental]
[webhosthidden]
delegate ITimer TimerFactory();

[experimental]
[webhosthidden]
DOC_STRING(
Expand Down Expand Up @@ -102,6 +107,9 @@ DOC_STRING("Settings per each IReactViewHost associated with an IReactHost insta
"This must be manually provided to the @ReactInstanceSettings object when using ReactNativeWindows"
"without XAML for certain APIs work correctly.")
static void SetTopLevelWindowId(IReactPropertyBag properties, UInt64 windowId);

DOC_STRING("Sets a factory method for creating custom timers, in environments where system dispatch timers should not be used.")
static void SetTimerFactory(IReactPropertyBag properties, TimerFactory timerFactory);
}


Expand Down
3 changes: 1 addition & 2 deletions vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -599,8 +599,7 @@ void ReactInstanceWin::InitializeBridgeless() noexcept {

m_jsMessageThread.Load()->runOnQueueSync([&]() {
::SetThreadDescription(GetCurrentThread(), L"React-Native JavaScript Thread");
auto timerRegistry = ::Microsoft::ReactNative::TimerRegistry::CreateTimerRegistry(
m_options.Properties.Get(ReactDispatcherHelper::UIDispatcherProperty()).try_as<IReactDispatcher>());
auto timerRegistry = ::Microsoft::ReactNative::TimerRegistry::CreateTimerRegistry(m_options.Properties);
auto timerRegistryRaw = timerRegistry.get();

auto timerManager = std::make_shared<facebook::react::TimerManager>(std::move(timerRegistry));
Expand Down
63 changes: 63 additions & 0 deletions vnext/Microsoft.ReactNative/Timer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#include "pch.h"
#include "Timer.h"
#include "Timer.g.cpp"

#include <CppWinRTIncludes.h>
#include "ReactCoreInjection.h"

namespace winrt::Microsoft::ReactNative::implementation {

// Implementation of ITimer based on Windows' DispatcherQueue
struct ReactTimer : winrt::implements<ReactTimer, ITimer> {
ReactTimer(const IReactPropertyBag &properties) {
const auto queue = winrt::dispatching::DispatcherQueue::GetForCurrentThread();
m_dispatcherQueueTimer = queue.CreateTimer();
}

winrt::Windows::Foundation::TimeSpan Interval() noexcept {
return m_dispatcherQueueTimer.Interval();
}

void Interval(winrt::Windows::Foundation::TimeSpan value) noexcept {
m_dispatcherQueueTimer.Interval(value);
}

void Start() noexcept {
return m_dispatcherQueueTimer.Start();
}

void Stop() noexcept {
return m_dispatcherQueueTimer.Stop();
}

winrt::event_token Tick(const winrt::Windows::Foundation::EventHandler<IInspectable> &handler) {
return m_dispatcherQueueTimer.Tick([handler](auto sender, auto args) { handler(sender, args); });
}

void Tick(winrt::event_token const &token) {
m_dispatcherQueueTimer.Tick(token);
}

private:
winrt::dispatching::DispatcherQueueTimer m_dispatcherQueueTimer{nullptr};
};

Timer::Timer() noexcept {}

ITimer Timer::Create(const IReactPropertyBag &properties) {
auto dispatcher = properties.Get(ReactDispatcherHelper::UIDispatcherProperty()).try_as<IReactDispatcher>();
if (!dispatcher.HasThreadAccess()) {
throw winrt::hresult_wrong_thread();
}

if (auto customTimerFactory = implementation::ReactCoreInjection::GetTimerFactory(properties)) {
return customTimerFactory();
} else {
return winrt::make<ReactTimer>(properties);
}
}

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

#pragma once

#include "Timer.g.h"

#include "winrt/Microsoft.ReactNative.h"

namespace winrt::Microsoft::ReactNative::implementation {

struct Timer : TimerT<Timer> {
Timer() noexcept;

static ITimer Create(const IReactPropertyBag &properties);
};

} // namespace winrt::Microsoft::ReactNative::implementation

namespace winrt::Microsoft::ReactNative::factory_implementation {
struct Timer : TimerT<Timer, implementation::Timer> {};
} // namespace winrt::Microsoft::ReactNative::factory_implementation
34 changes: 34 additions & 0 deletions vnext/Microsoft.ReactNative/Timer.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#include "DocString.h"
import "IJSValueReader.idl";
import "IJSValueWriter.idl";
import "Theme.idl";

namespace Microsoft.ReactNative {

[webhosthidden]
interface ITimer
{
Windows.Foundation.TimeSpan Interval;
void Start();
void Stop();

event Windows.Foundation.EventHandler<Object> Tick;
}

[default_interface]
[webhosthidden]
DOC_STRING(
"Used to create timers.")
runtimeclass Timer
{
DOC_STRING(
"Creates a UI timer. Must be called on the UI thread. Using this rather than "
"System/Windows.DispatcherQueue.CreateTimer works when applications have provided "
"custom Timer implementations using @ReactCoreInjection.SetTimerFactory")
static ITimer Create(IReactPropertyBag properties);
}

} // namespace Microsoft.ReactNative
4 changes: 4 additions & 0 deletions vnext/Shared/Shared.vcxitems
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,9 @@
<ClCompile Include="$(MSBuildThisFileDirectory)..\Microsoft.ReactNative\JSDispatcherWriter.cpp">
<DependentUpon>$(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IJSValueWriter.idl</DependentUpon>
</ClCompile>
<ClCompile Include="$(MSBuildThisFileDirectory)..\Microsoft.ReactNative\Timer.cpp">
<DependentUpon>$(MSBuildThisFileDirectory)..\Microsoft.ReactNative\Timer.idl</DependentUpon>
</ClCompile>
<ClInclude Include="$(MSBuildThisFileDirectory)AbiSafe.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)BaseFileReaderResource.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)CppRuntimeOptions.h" />
Expand Down Expand Up @@ -605,6 +608,7 @@
<Midl Include="$(MSBuildThisFileDirectory)..\Microsoft.ReactNative\ReactInstanceSettings.idl" />
<Midl Include="$(MSBuildThisFileDirectory)..\Microsoft.ReactNative\ReactNativeHost.idl" />
<Midl Include="$(MSBuildThisFileDirectory)..\Microsoft.ReactNative\RedBoxHandler.idl" />
<Midl Include="$(MSBuildThisFileDirectory)..\Microsoft.ReactNative\Timer.idl" />
</ItemGroup>
<ItemGroup Condition="'$(UseFabric)' == 'true' OR '$(IncludeFabricInterface)' == 'true'">
<Midl Condition="'$(UseFabric)' == 'true' OR '$(IncludeFabricInterface)' == 'true'" Include="$(ReactNativeWindowsDir)Microsoft.ReactNative\ComponentView.idl" />
Expand Down

0 comments on commit 34c5cd8

Please sign in to comment.