Skip to content

Commit 124def4

Browse files
committed
Merge with new timeout code with work queue fixes
1 parent 9d1e098 commit 124def4

File tree

11 files changed

+106
-71
lines changed

11 files changed

+106
-71
lines changed

Core/AppRuntime/Source/AppRuntime.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ namespace Babylon
1212
: m_workQueue{std::make_unique<WorkQueue>([this] { RunPlatformTier(); })}
1313
, m_unhandledExceptionHandler{unhandledExceptionHandler}
1414
{
15-
Dispatch([this](Napi::Env env) {
15+
Dispatch([this](Napi::Env env)
16+
{
1617
JsRuntime::CreateForJavaScript(env, [this](auto func) { Dispatch(std::move(func)); });
1718
});
1819
}
@@ -45,8 +46,10 @@ namespace Babylon
4546

4647
void AppRuntime::Dispatch(Dispatchable<void(Napi::Env)> func)
4748
{
48-
m_workQueue->Append([this, func{std::move(func)}](Napi::Env env) mutable {
49-
Execute([this, env, func{std::move(func)}]() mutable {
49+
m_workQueue->Append([this, func{std::move(func)}](Napi::Env env) mutable
50+
{
51+
Execute([this, env, func{std::move(func)}]() mutable
52+
{
5053
try
5154
{
5255
func(env);

Core/AppRuntime/Source/WorkQueue.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ namespace Babylon
4848
m_dispatcher.blocking_tick(m_cancellationSource);
4949
}
5050

51-
// There should not be any outstanding work during the shutdown sequence
52-
// which should be the only way exit the while loop above.
51+
// Drain the queue to complete work dispatched after cancellation.
52+
m_dispatcher.tick(arcana::cancellation::none());
53+
54+
// There should no longer be any outstanding work once the queue is drained.
5355
assert(m_dispatcher.empty());
5456
}
5557
}

Core/AppRuntime/Source/WorkQueue.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace Babylon
2222
if (m_cancellationSource.cancelled())
2323
{
2424
// There is likely a coding error if this exception is thrown.
25-
throw std::runtime_error{"Cannot append to the work queue after it is canceled"};
25+
throw std::runtime_error{"Cannot append to the work queue while shutting down"};
2626
}
2727

2828
// Manual dispatcher queueing requires a copyable CallableT, we use a shared pointer trick to make a
@@ -57,6 +57,6 @@ namespace Babylon
5757
arcana::cancellation_source m_cancellationSource{};
5858
arcana::manual_dispatcher<128> m_dispatcher{};
5959

60-
std::thread m_thread;
60+
std::thread m_thread{};
6161
};
6262
}

Core/JsRuntime/Include/Babylon/JsRuntime.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,20 @@ namespace Babylon
4242
// Gets the JsRuntime from the given N-API environment.
4343
static JsRuntime& GetFromJavaScript(Napi::Env);
4444

45+
struct IDisposingCallback
46+
{
47+
virtual void Disposing() = 0;
48+
};
49+
4550
// Notifies the JsRuntime that the JavaScript environment will begin shutting down.
4651
// Calling this function will signal callbacks registered with RegisterDisposing.
4752
static void NotifyDisposing(JsRuntime&);
4853

4954
// Registers a callback for when the JavaScript environment will begin shutting down.
50-
static void RegisterDisposing(JsRuntime&, std::function<void()>);
55+
static void RegisterDisposing(JsRuntime&, IDisposingCallback*);
56+
57+
// Unregisters a callback for when the JavaScript environment will begin shutting down.
58+
static void UnregisterDisposing(JsRuntime&, IDisposingCallback*);
5159

5260
// Dispatches work onto the JavaScript thread and provides access to the N-API
5361
// environment.
@@ -58,6 +66,6 @@ namespace Babylon
5866

5967
std::mutex m_mutex{};
6068
DispatchFunctionT m_dispatchFunction{};
61-
std::vector<std::function<void()>> m_disposingCallbacks{};
69+
std::vector<IDisposingCallback*> m_disposingCallbacks{};
6270
};
6371
}

Core/JsRuntime/Include/Babylon/JsRuntimeScheduler.h

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,26 @@ namespace Babylon
5353
// the continuation, must capture a persistent reference to the N-API object itself to prevent the GC from
5454
// collecting the N-API object during the asynchronous operation. Failing to do so will result in a hang
5555
// when `Rundown()` is called in the N-API class destructor.
56-
class JsRuntimeScheduler
56+
class JsRuntimeScheduler : public JsRuntime::IDisposingCallback
5757
{
5858
public:
5959
explicit JsRuntimeScheduler(JsRuntime& runtime)
6060
: m_runtime{&runtime}
6161
, m_scheduler{*this}
6262
{
63-
JsRuntime::RegisterDisposing(*m_runtime, [this]()
64-
{
65-
m_runtime = nullptr;
66-
});
63+
std::scoped_lock lock{m_mutex};
64+
JsRuntime::RegisterDisposing(*m_runtime, this);
6765
}
6866

6967
~JsRuntimeScheduler()
7068
{
7169
assert(m_count == 0 && "Schedulers for the JavaScript thread are not yet invoked");
70+
71+
std::scoped_lock lock{m_mutex};
72+
if (m_runtime != nullptr)
73+
{
74+
JsRuntime::UnregisterDisposing(*m_runtime, this);
75+
}
7276
}
7377

7478
// Wait until all of the schedulers are invoked.
@@ -88,6 +92,12 @@ namespace Babylon
8892
}
8993

9094
private:
95+
void Disposing() override
96+
{
97+
std::scoped_lock lock{m_mutex};
98+
m_runtime = nullptr;
99+
}
100+
91101
class SchedulerImpl
92102
{
93103
public:
@@ -108,6 +118,8 @@ namespace Babylon
108118
template<typename CallableT>
109119
void Dispatch(CallableT&& callable)
110120
{
121+
std::scoped_lock lock{m_mutex};
122+
111123
if (m_runtime != nullptr)
112124
{
113125
m_runtime->Dispatch([callable{std::forward<CallableT>(callable)}](Napi::Env)
@@ -123,7 +135,10 @@ namespace Babylon
123135
--m_count;
124136
}
125137

126-
JsRuntime* m_runtime;
138+
mutable std::mutex m_mutex{};
139+
JsRuntime* m_runtime{};
140+
std::function<void()> m_disposingCallback{};
141+
127142
SchedulerImpl m_scheduler;
128143
std::atomic<int> m_count{0};
129144
arcana::manual_dispatcher<128> m_disposingDispatcher{};

Core/JsRuntime/Source/JsRuntime.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "JsRuntime.h"
2+
#include <cassert>
23

34
namespace Babylon
45
{
@@ -43,15 +44,27 @@ namespace Babylon
4344
void JsRuntime::NotifyDisposing(JsRuntime& runtime)
4445
{
4546
auto callbacks = std::move(runtime.m_disposingCallbacks);
46-
for (const auto& callback : callbacks)
47+
for (auto* callback : callbacks)
4748
{
48-
callback();
49+
callback->Disposing();
4950
}
5051
}
5152

52-
void JsRuntime::RegisterDisposing(JsRuntime& runtime, std::function<void()> callback)
53+
void JsRuntime::RegisterDisposing(JsRuntime& runtime, IDisposingCallback* callback)
5354
{
54-
runtime.m_disposingCallbacks.push_back(std::move(callback));
55+
auto& callbacks = runtime.m_disposingCallbacks;
56+
assert(std::find(callbacks.begin(), callbacks.end(), callback) == callbacks.end());
57+
callbacks.push_back(callback);
58+
}
59+
60+
void JsRuntime::UnregisterDisposing(JsRuntime& runtime, IDisposingCallback* callback)
61+
{
62+
auto& callbacks = runtime.m_disposingCallbacks;
63+
auto it = std::find(callbacks.begin(), callbacks.end(), callback);
64+
if (it != callbacks.end())
65+
{
66+
callbacks.erase(it);
67+
}
5568
}
5669

5770
void JsRuntime::Dispatch(std::function<void(Napi::Env)> function)

Plugins/NativeEngine/Source/NativeEngine.cpp

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,26 +1067,25 @@ namespace Babylon
10671067
});
10681068
}
10691069

1070-
void NativeEngine::CopyTexture(const Napi::CallbackInfo& /*info*/)
1071-
{
1072-
//const auto textureDestination = info[0].As<Napi::Pointer<Graphics::Texture>>().Get();
1073-
//const auto textureSource = info[1].As<Napi::Pointer<Graphics::Texture>>().Get();
1074-
1075-
// TODO: this code makes no sense
1076-
//arcana::make_task(m_graphicsUpdate.Scheduler(), m_cancellationSource, [this, textureDestination, textureSource]()
1077-
//{
1078-
// return arcana::make_task(m_runtimeScheduler.Get(), m_cancellationSource, [this, textureDestination, textureSource, updateToken = m_graphicsUpdate.GetUpdateToken()]() mutable
1079-
// {
1080-
// bgfx::Encoder* encoder = updateToken.GetEncoder();
1081-
// GetBoundFrameBuffer(*encoder).Blit(*encoder, textureDestination->Handle(), 0, 0, textureSource->Handle());
1082-
// }).then(arcana::inline_scheduler, m_cancellationSource, [this, thisRef = Napi::Persistent(info.Env())](const arcana::expected<void, std::exception_ptr>& result)
1083-
// {
1084-
// if (result.has_error())
1085-
// {
1086-
// Napi::Error::New(Env(), result.error()).ThrowAsJavaScriptException();
1087-
// }
1088-
// });
1089-
//});
1070+
void NativeEngine::CopyTexture(const Napi::CallbackInfo& info)
1071+
{
1072+
const auto textureDestination = info[0].As<Napi::Pointer<Graphics::Texture>>().Get();
1073+
const auto textureSource = info[1].As<Napi::Pointer<Graphics::Texture>>().Get();
1074+
1075+
arcana::make_task(m_update.Scheduler(), m_cancellationSource, [this, textureDestination, textureSource]() mutable
1076+
{
1077+
return arcana::make_task(m_runtimeScheduler.Get(), m_cancellationSource, [this, textureDestination, textureSource, updateToken = m_update.GetUpdateToken()]() mutable
1078+
{
1079+
bgfx::Encoder* encoder = updateToken.GetEncoder();
1080+
GetBoundFrameBuffer(*encoder).Blit(*encoder, textureDestination->Handle(), 0, 0, textureSource->Handle());
1081+
}).then(arcana::inline_scheduler, m_cancellationSource, [this](const arcana::expected<void, std::exception_ptr>& result)
1082+
{
1083+
if (result.has_error())
1084+
{
1085+
Napi::Error::New(Env(), result.error()).ThrowAsJavaScriptException();
1086+
}
1087+
});
1088+
});
10901089
}
10911090

10921091
void NativeEngine::LoadRawTexture(const Napi::CallbackInfo& info)

Polyfills/Window/Source/TimeoutDispatcher.cpp

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,26 @@ namespace Babylon::Polyfills::Internal
3434
Timeout(Timeout&&) = delete;
3535
};
3636

37-
TimeoutDispatcher::TimeoutDispatcher(Babylon::JsRuntimeScheduler& runtimeScheduler)
38-
: m_runtimeScheduler{runtimeScheduler}
37+
TimeoutDispatcher::TimeoutDispatcher(Babylon::JsRuntime& runtime)
38+
: m_runtimeScheduler{runtime}
3939
, m_thread{std::thread{&TimeoutDispatcher::ThreadFunction, this}}
4040
{
4141
}
4242

4343
TimeoutDispatcher::~TimeoutDispatcher()
4444
{
4545
{
46-
std::unique_lock<std::mutex> lk{m_mutex};
46+
std::unique_lock<std::mutex> lock{m_mutex};
4747
m_idMap.clear();
4848
m_timeMap.clear();
4949
}
5050

51-
m_shutdown = true;
51+
m_cancellationSource.cancel();
5252
m_condVariable.notify_one();
5353
m_thread.join();
54+
55+
// Wait for async operations to complete.
56+
m_runtimeScheduler.Rundown();
5457
}
5558

5659
TimeoutDispatcher::TimeoutId TimeoutDispatcher::Dispatch(std::shared_ptr<Napi::FunctionReference> function, std::chrono::milliseconds delay)
@@ -71,7 +74,8 @@ namespace Babylon::Polyfills::Internal
7174

7275
if (time <= earliestTime)
7376
{
74-
m_runtimeScheduler.Get()([this]() {
77+
m_runtimeScheduler.Get()([this]()
78+
{
7579
m_condVariable.notify_one();
7680
});
7781
}
@@ -123,11 +127,11 @@ namespace Babylon::Polyfills::Internal
123127

124128
void TimeoutDispatcher::ThreadFunction()
125129
{
126-
while (!m_shutdown)
130+
while (!m_cancellationSource.cancelled())
127131
{
128-
std::unique_lock<std::mutex> lk{m_mutex};
129-
TimePoint nextTimePoint{};
132+
std::unique_lock<std::mutex> lock{m_mutex};
130133

134+
TimePoint nextTimePoint{};
131135
while (!m_timeMap.empty())
132136
{
133137
nextTimePoint = m_timeMap.begin()->second->time;
@@ -136,7 +140,7 @@ namespace Babylon::Polyfills::Internal
136140
break;
137141
}
138142

139-
m_condVariable.wait_until(lk, nextTimePoint);
143+
m_condVariable.wait_until(lock, nextTimePoint);
140144
}
141145

142146
while (!m_timeMap.empty() && m_timeMap.begin()->second->time == nextTimePoint)
@@ -147,9 +151,9 @@ namespace Babylon::Polyfills::Internal
147151
m_idMap.erase(timeout->id);
148152
}
149153

150-
while (!m_shutdown && m_timeMap.empty())
154+
while (!m_cancellationSource.cancelled() && m_timeMap.empty())
151155
{
152-
m_condVariable.wait(lk);
156+
m_condVariable.wait(lock);
153157
}
154158
}
155159
}
@@ -159,7 +163,9 @@ namespace Babylon::Polyfills::Internal
159163
if (function)
160164
{
161165
m_runtimeScheduler.Get()([function = std::move(function)]()
162-
{ function->Call({}); });
166+
{
167+
function->Call({});
168+
});
163169
}
164170
}
165171
}

Polyfills/Window/Source/TimeoutDispatcher.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace Babylon::Polyfills::Internal
1919
struct Timeout;
2020

2121
public:
22-
TimeoutDispatcher(Babylon::JsRuntimeScheduler& runtimeScheduler);
22+
TimeoutDispatcher(Babylon::JsRuntime& runtime);
2323
~TimeoutDispatcher();
2424

2525
TimeoutId Dispatch(std::shared_ptr<Napi::FunctionReference> function, std::chrono::milliseconds delay);
@@ -32,13 +32,14 @@ namespace Babylon::Polyfills::Internal
3232
void ThreadFunction();
3333
void CallFunction(std::shared_ptr<Napi::FunctionReference> function);
3434

35-
Babylon::JsRuntimeScheduler& m_runtimeScheduler;
36-
std::mutex m_mutex{};
35+
Babylon::JsRuntimeScheduler m_runtimeScheduler;
36+
37+
mutable std::mutex m_mutex{};
3738
std::condition_variable m_condVariable{};
3839
TimeoutId m_lastTimeoutId{0};
39-
std::unordered_map<TimeoutId, std::unique_ptr<Timeout>> m_idMap;
40-
std::multimap<TimePoint, Timeout*> m_timeMap;
41-
std::atomic<bool> m_shutdown{false};
42-
std::thread m_thread;
40+
std::unordered_map<TimeoutId, std::unique_ptr<Timeout>> m_idMap{};
41+
std::multimap<TimePoint, Timeout*> m_timeMap{};
42+
arcana::cancellation_source m_cancellationSource{};
43+
std::thread m_thread{};
4344
};
4445
}

Polyfills/Window/Source/Window.cpp

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,10 @@ namespace Babylon::Polyfills::Internal
7171

7272
Window::Window(const Napi::CallbackInfo& info)
7373
: Napi::ObjectWrap<Window>{info}
74-
, m_runtimeScheduler{JsRuntime::GetFromJavaScript(info.Env())}
75-
, m_timeoutDispatcher{m_runtimeScheduler}
74+
, m_timeoutDispatcher{JsRuntime::GetFromJavaScript(info.Env())}
7675
{
7776
}
7877

79-
Window::~Window()
80-
{
81-
m_cancelSource.cancel();
82-
83-
// Wait for async operations to complete.
84-
m_runtimeScheduler.Rundown();
85-
}
86-
8778
Napi::Value Window::SetTimeout(const Napi::CallbackInfo& info)
8879
{
8980
auto& window = *static_cast<Window*>(info.Data());

0 commit comments

Comments
 (0)