Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 11 additions & 29 deletions Core/AppRuntime/Include/Babylon/AppRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,9 @@

#include <napi/utilities.h>

#include <arcana/threading/cancellation.h>
#include <arcana/threading/dispatcher.h>

#include <memory>
#include <functional>
#include <exception>
#include <optional>
#include <mutex>
#include <thread>
#include <type_traits>

namespace Babylon
{
Expand All @@ -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();

Expand All @@ -48,23 +49,6 @@ namespace Babylon
static void BABYLON_API DefaultUnhandledExceptionHandler(const Napi::Error& error);

private:
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());
});
}
}

// 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
Expand All @@ -84,10 +68,8 @@ namespace Babylon
void Execute(Dispatchable<void()> callback);

Options m_options;
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::thread m_thread;

class Impl;
std::unique_ptr<Impl> m_impl;
};
}
67 changes: 52 additions & 15 deletions Core/AppRuntime/Source/AppRuntime.cpp
Original file line number Diff line number Diff line change
@@ -1,27 +1,64 @@
#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::thread m_thread;
};

AppRuntime::AppRuntime() :
AppRuntime{{}}
{
}

AppRuntime::AppRuntime(Options options)
: m_options{std::move(options)}
, m_thread{[this] { RunPlatformTier(); }}
, 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_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
Expand All @@ -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<std::mutex>();
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<void(Napi::Env)> 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
{
Expand Down
Loading