-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathAppRuntime.cpp
More file actions
138 lines (120 loc) · 4.12 KB
/
AppRuntime.cpp
File metadata and controls
138 lines (120 loc) · 4.12 KB
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
#include "AppRuntime.h"
#include <arcana/threading/cancellation.h>
#include <arcana/threading/dispatcher.h>
#include <cassert>
#include <optional>
#include <mutex>
#include <thread>
#include <type_traits>
namespace Babylon
{
class AppRuntime::Impl
{
public:
template<typename CallableT>
void Append(CallableT callable)
{
if constexpr (std::is_copy_constructible<CallableT>::value)
{
m_dispatcher.queue([this, callable = std::move(callable)]() {
callable(m_env.value());
});
}
else
{
m_dispatcher.queue([this, callablePtr = std::make_shared<CallableT>(std::move(callable))]() {
(*callablePtr)(m_env.value());
});
}
}
std::optional<Napi::Env> m_env{};
std::optional<std::scoped_lock<std::mutex>> m_suspensionLock{};
arcana::cancellation_source m_cancelSource{};
arcana::manual_dispatcher<128> m_dispatcher{};
std::function<void()> m_postTickCallback{};
std::thread m_thread;
};
AppRuntime::AppRuntime() :
AppRuntime{{}}
{
}
AppRuntime::AppRuntime(Options options)
: m_options{std::move(options)}
, m_impl{std::make_unique<Impl>()}
{
m_impl->m_thread = std::thread{[this] { RunPlatformTier(); }};
Dispatch([this](Napi::Env env) {
JsRuntime::CreateForJavaScript(env, [this](auto func) { Dispatch(std::move(func)); });
});
}
AppRuntime::~AppRuntime()
{
if (m_impl->m_suspensionLock.has_value())
{
m_impl->m_suspensionLock.reset();
}
// Cancel immediately so pending work is dropped promptly, then append
// a no-op work item to wake the worker thread from blocking_tick. The
// no-op goes through push() which acquires the queue mutex, avoiding
// the race where a bare notify_all() can be missed by wait().
//
// NOTE: This preserves the existing shutdown behavior where pending
// callbacks are dropped on cancellation. A more complete solution
// would add cooperative shutdown (e.g. NotifyDisposing/Rundown) so
// consumers can finish cleanup work before the runtime is destroyed.
m_impl->m_cancelSource.cancel();
m_impl->Append([](Napi::Env) {});
m_impl->m_thread.join();
}
void AppRuntime::Run(Napi::Env env)
{
m_impl->m_env = std::make_optional(env);
m_impl->m_dispatcher.set_affinity(std::this_thread::get_id());
while (!m_impl->m_cancelSource.cancelled())
{
m_impl->m_dispatcher.blocking_tick(m_impl->m_cancelSource);
if (m_impl->m_postTickCallback)
{
m_impl->m_postTickCallback();
}
}
// The dispatcher can be non-empty if something is dispatched after cancellation.
m_impl->m_dispatcher.clear();
}
void AppRuntime::Suspend()
{
auto suspensionMutex = std::make_shared<std::mutex>();
m_impl->m_suspensionLock.emplace(*suspensionMutex);
m_impl->Append([suspensionMutex{std::move(suspensionMutex)}](Napi::Env) {
std::scoped_lock lock{*suspensionMutex};
});
}
void AppRuntime::Resume()
{
m_impl->m_suspensionLock.reset();
}
void AppRuntime::SetPostTickCallback(std::function<void()> callback)
{
m_impl->m_postTickCallback = std::move(callback);
}
void AppRuntime::Dispatch(Dispatchable<void(Napi::Env)> func)
{
m_impl->Append([this, func{std::move(func)}](Napi::Env env) mutable {
Execute([this, env, func{std::move(func)}]() mutable {
try
{
func(env);
}
catch (const Napi::Error& error)
{
m_options.UnhandledExceptionHandler(error);
}
catch (...)
{
assert(false);
std::abort();
}
});
});
}
}