-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathTimingModule.cpp
268 lines (219 loc) · 7.48 KB
/
TimingModule.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "pch.h"
#undef Check
#include "TimingModule.h"
#include <InstanceManager.h>
#include <UI.Xaml.Media.h>
#include <Utils/ValueUtils.h>
#include <cxxreact/CxxModule.h>
#include <cxxreact/Instance.h>
#include <cxxreact/JsArgumentHelpers.h>
#include <unknwnbase.h>
using namespace facebook::xplat;
using namespace folly;
namespace winrt {
using namespace Windows::Foundation;
using namespace xaml::Media;
} // namespace winrt
using namespace std;
namespace Microsoft::ReactNative {
//
// IsAnimationFrameRequest
//
static bool IsAnimationFrameRequest(TTimeSpan period, bool repeat) {
return !repeat && period == std::chrono::milliseconds(1);
}
//
// TimerQueue
//
TimerQueue::TimerQueue() {
std::make_heap(m_timerVector.begin(), m_timerVector.end());
}
void TimerQueue::Push(int64_t id, TDateTime targetTime, TTimeSpan period, bool repeat) {
m_timerVector.emplace_back(id, targetTime, period, repeat);
std::push_heap(m_timerVector.begin(), m_timerVector.end());
}
void TimerQueue::Pop() {
std::pop_heap(m_timerVector.begin(), m_timerVector.end());
m_timerVector.pop_back();
}
Timer &TimerQueue::Front() {
return m_timerVector.front();
}
void TimerQueue::Remove(int64_t id) {
// TODO: This is very inefficient, but doing this with a heap is inherently
// hard. If performance is not good
// enough for the scenarios then a different structure is probably needed.
auto found = std::find(m_timerVector.begin(), m_timerVector.end(), id);
if (found != m_timerVector.end())
m_timerVector.erase(found);
std::make_heap(m_timerVector.begin(), m_timerVector.end());
}
bool TimerQueue::IsEmpty() {
return m_timerVector.empty();
}
//
// Timing
//
Timing::Timing(TimingModule *parent) : m_parent(parent) {}
void Timing::Disconnect() {
m_parent = nullptr;
StopTicks();
}
std::weak_ptr<facebook::react::Instance> Timing::getInstance() noexcept {
if (!m_parent)
return std::weak_ptr<facebook::react::Instance>();
return m_parent->getInstance();
}
void Timing::OnTick() {
std::vector<int64_t> readyTimers;
auto now = TDateTime::clock::now();
auto emittedAnimationFrame = false;
while (!m_timerQueue.IsEmpty() && m_timerQueue.Front().TargetTime < now) {
// Pop first timer from the queue and add it to list of timers ready to fire
Timer next = m_timerQueue.Front();
m_timerQueue.Pop();
readyTimers.push_back(next.Id);
// If timer is repeating push it back onto the queue for the next repetition
if (next.Repeat)
m_timerQueue.Push(next.Id, now + next.Period, next.Period, true);
else if (IsAnimationFrameRequest(next.Period, next.Repeat))
emittedAnimationFrame = true;
}
if (m_timerQueue.IsEmpty()) {
StopTicks();
} else if (!m_usingRendering || !emittedAnimationFrame) {
// If we're using a rendering callback, check if any animation frame
// requests were emitted in this tick. If not, start the dispatcher timer.
// This extra frame gives JS a chance to enqueue an additional animation
// frame request before switching tick schedulers.
StartDispatcherTimer();
}
if (!readyTimers.empty()) {
if (auto instance = getInstance().lock()) {
// Package list of Timer Ids to fire in a dynamic array to pass as
// parameter
folly::dynamic params = folly::dynamic::array();
for (size_t i = 0, c = readyTimers.size(); i < c; ++i)
params.push_back(folly::dynamic(readyTimers[i]));
instance->callJSFunction("JSTimers", "callTimers", folly::dynamic::array(params));
}
}
}
winrt::system::DispatcherQueueTimer Timing::EnsureDispatcherTimer() {
if (!m_dispatcherQueueTimer) {
const auto queue = winrt::system::DispatcherQueue::GetForCurrentThread();
m_dispatcherQueueTimer = queue.CreateTimer();
m_dispatcherQueueTimer.Tick([wkThis = std::weak_ptr(this->shared_from_this())](auto &&...) {
if (auto pThis = wkThis.lock()) {
pThis->OnTick();
}
});
}
return m_dispatcherQueueTimer;
}
void Timing::StartRendering() {
if (m_dispatcherQueueTimer)
m_dispatcherQueueTimer.Stop();
m_rendering.revoke();
m_usingRendering = true;
m_rendering = xaml::Media::CompositionTarget::Rendering(
winrt::auto_revoke,
[wkThis = std::weak_ptr(this->shared_from_this())](
const winrt::IInspectable &, const winrt::IInspectable & /*args*/) {
if (auto pThis = wkThis.lock()) {
pThis->OnTick();
}
});
}
void Timing::StartDispatcherTimer() {
const auto &nextTimer = m_timerQueue.Front();
m_rendering.revoke();
m_usingRendering = false;
auto timer = EnsureDispatcherTimer();
timer.Interval(std::max(nextTimer.TargetTime - TDateTime::clock::now(), TTimeSpan::zero()));
timer.Start();
}
void Timing::StopTicks() {
m_rendering.revoke();
m_usingRendering = false;
if (m_dispatcherQueueTimer)
m_dispatcherQueueTimer.Stop();
}
void Timing::createTimer(int64_t id, double duration, double jsSchedulingTime, bool repeat) {
if (duration == 0 && !repeat) {
if (auto instance = getInstance().lock()) {
folly::dynamic params = folly::dynamic::array(id);
instance->callJSFunction("JSTimers", "callTimers", folly::dynamic::array(params));
}
return;
}
// Convert double duration in ms to TimeSpan
auto period = TimeSpanFromMs(duration);
const int64_t msFrom1601to1970 = 11644473600000;
TDateTime scheduledTime(TimeSpanFromMs(jsSchedulingTime + msFrom1601to1970));
auto initialTargetTime = scheduledTime + period;
m_timerQueue.Push(id, initialTargetTime, period, repeat);
if (!m_usingRendering) {
if (IsAnimationFrameRequest(period, repeat)) {
StartRendering();
} else if (initialTargetTime <= m_timerQueue.Front().TargetTime) {
StartDispatcherTimer();
}
}
}
void Timing::deleteTimer(int64_t id) {
m_timerQueue.Remove(id);
if (m_timerQueue.IsEmpty())
StopTicks();
}
void Timing::setSendIdleEvents(bool /*sendIdleEvents*/) {
// TODO: Implement.
}
//
// TimingModule
//
const char *TimingModule::name = "Timing";
TimingModule::TimingModule() : m_timing(std::make_shared<Timing>(this)) {}
TimingModule::~TimingModule() {
if (m_timing != nullptr)
m_timing->Disconnect();
}
std::string TimingModule::getName() {
return name;
}
auto TimingModule::getConstants() -> std::map<std::string, dynamic> {
return {};
}
auto TimingModule::getMethods() -> std::vector<Method> {
std::shared_ptr<Timing> timing(m_timing);
return {
Method(
"createTimer",
[timing](dynamic args) // int64_t id, double duration, double
// jsSchedulingTime, bool repeat
{
timing->createTimer(
jsArgAsInt(args, 0), jsArgAsDouble(args, 1), jsArgAsDouble(args, 2), jsArgAsBool(args, 3));
}),
Method(
"deleteTimer",
[timing](dynamic args) // int64_t code, const std::string& reason,
// int64_t id
{ timing->deleteTimer(jsArgAsInt(args, 0)); }),
Method(
"setSendIdleEvents",
[timing](dynamic args) // const std::string& message, int64_t id
{ timing->setSendIdleEvents(jsArgAsBool(args, 0)); }),
};
}
} // namespace Microsoft::ReactNative
namespace facebook {
namespace react {
std::unique_ptr<facebook::xplat::module::CxxModule> CreateTimingModule(
const std::shared_ptr<facebook::react::MessageQueueThread> &) noexcept {
return std::make_unique<Microsoft::ReactNative::TimingModule>();
}
} // namespace react
} // namespace facebook