diff --git a/Core/AppRuntime/Include/Babylon/AppRuntime.h b/Core/AppRuntime/Include/Babylon/AppRuntime.h index 0de98b8d..f6ca7dfd 100644 --- a/Core/AppRuntime/Include/Babylon/AppRuntime.h +++ b/Core/AppRuntime/Include/Babylon/AppRuntime.h @@ -6,16 +6,9 @@ #include -#include -#include - #include #include #include -#include -#include -#include -#include namespace Babylon { @@ -39,6 +32,14 @@ namespace Babylon AppRuntime(Options options); ~AppRuntime(); + // Copy semantics + AppRuntime(const AppRuntime&) = delete; + AppRuntime& operator=(const AppRuntime&) = delete; + + // Move semantics + AppRuntime(AppRuntime&&) = delete; + AppRuntime& operator=(AppRuntime&&) = delete; + void Suspend(); void Resume(); @@ -48,23 +49,6 @@ namespace Babylon static void BABYLON_API DefaultUnhandledExceptionHandler(const Napi::Error& error); private: - template - void Append(CallableT callable) - { - if constexpr (std::is_copy_constructible::value) - { - m_dispatcher.queue([this, callable = std::move(callable)]() { - callable(m_env.value()); - }); - } - else - { - m_dispatcher.queue([this, callablePtr = std::make_shared(std::move(callable))]() { - (*callablePtr)(m_env.value()); - }); - } - } - // These three methods are the mechanism by which platform- and JavaScript-specific // code can be "injected" into the execution of the JavaScript thread. These three // functions are implemented in separate files, thus allowing implementations to be @@ -84,10 +68,8 @@ namespace Babylon void Execute(Dispatchable callback); Options m_options; - std::optional m_env{}; - std::optional> m_suspensionLock{}; - arcana::cancellation_source m_cancelSource{}; - arcana::manual_dispatcher<128> m_dispatcher{}; - std::thread m_thread; + + class Impl; + std::unique_ptr m_impl; }; } diff --git a/Core/AppRuntime/Source/AppRuntime.cpp b/Core/AppRuntime/Source/AppRuntime.cpp index ae1ee4f9..99298df2 100644 --- a/Core/AppRuntime/Source/AppRuntime.cpp +++ b/Core/AppRuntime/Source/AppRuntime.cpp @@ -1,8 +1,43 @@ #include "AppRuntime.h" + +#include +#include + #include +#include +#include +#include +#include namespace Babylon { + class AppRuntime::Impl + { + public: + template + void Append(CallableT callable) + { + if constexpr (std::is_copy_constructible::value) + { + m_dispatcher.queue([this, callable = std::move(callable)]() { + callable(m_env.value()); + }); + } + else + { + m_dispatcher.queue([this, callablePtr = std::make_shared(std::move(callable))]() { + (*callablePtr)(m_env.value()); + }); + } + } + + std::optional m_env{}; + std::optional> m_suspensionLock{}; + arcana::cancellation_source m_cancelSource{}; + arcana::manual_dispatcher<128> m_dispatcher{}; + std::thread m_thread; + }; + AppRuntime::AppRuntime() : AppRuntime{{}} { @@ -10,8 +45,10 @@ namespace Babylon AppRuntime::AppRuntime(Options options) : m_options{std::move(options)} - , m_thread{[this] { RunPlatformTier(); }} + , m_impl{std::make_unique()} { + m_impl->m_thread = std::thread{[this] { RunPlatformTier(); }}; + Dispatch([this](Napi::Env env) { JsRuntime::CreateForJavaScript(env, [this](auto func) { Dispatch(std::move(func)); }); }); @@ -19,9 +56,9 @@ namespace Babylon AppRuntime::~AppRuntime() { - if (m_suspensionLock.has_value()) + if (m_impl->m_suspensionLock.has_value()) { - m_suspensionLock.reset(); + m_impl->m_suspensionLock.reset(); } // Cancel immediately so pending work is dropped promptly, then append @@ -33,44 +70,44 @@ namespace Babylon // 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_cancelSource.cancel(); - Append([](Napi::Env) {}); + m_impl->m_cancelSource.cancel(); + m_impl->Append([](Napi::Env) {}); - m_thread.join(); + m_impl->m_thread.join(); } void AppRuntime::Run(Napi::Env env) { - m_env = std::make_optional(env); + m_impl->m_env = std::make_optional(env); - m_dispatcher.set_affinity(std::this_thread::get_id()); + m_impl->m_dispatcher.set_affinity(std::this_thread::get_id()); - while (!m_cancelSource.cancelled()) + while (!m_impl->m_cancelSource.cancelled()) { - m_dispatcher.blocking_tick(m_cancelSource); + m_impl->m_dispatcher.blocking_tick(m_impl->m_cancelSource); } // The dispatcher can be non-empty if something is dispatched after cancellation. - m_dispatcher.clear(); + m_impl->m_dispatcher.clear(); } void AppRuntime::Suspend() { auto suspensionMutex = std::make_shared(); - m_suspensionLock.emplace(*suspensionMutex); - Append([suspensionMutex{std::move(suspensionMutex)}](Napi::Env) { + m_impl->m_suspensionLock.emplace(*suspensionMutex); + m_impl->Append([suspensionMutex{std::move(suspensionMutex)}](Napi::Env) { std::scoped_lock lock{*suspensionMutex}; }); } void AppRuntime::Resume() { - m_suspensionLock.reset(); + m_impl->m_suspensionLock.reset(); } void AppRuntime::Dispatch(Dispatchable func) { - Append([this, func{std::move(func)}](Napi::Env env) mutable { + m_impl->Append([this, func{std::move(func)}](Napi::Env env) mutable { Execute([this, env, func{std::move(func)}]() mutable { try {