Skip to content
Open
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
8 changes: 7 additions & 1 deletion Core/AppRuntime/Source/AppRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ namespace Babylon
}

std::optional<Napi::Env> m_env{};
std::shared_ptr<JsRuntime::InternalState> m_jsRuntimeState{};
std::optional<std::scoped_lock<std::mutex>> m_suspensionLock{};
arcana::cancellation_source m_cancelSource{};
arcana::manual_dispatcher<128> m_dispatcher{};
Expand All @@ -54,7 +55,7 @@ namespace Babylon
m_impl->m_thread = std::thread{[this] { RunPlatformTier(); }};

Dispatch([this](Napi::Env env) {
JsRuntime::CreateForJavaScript(env, [this](auto func) { Dispatch(std::move(func)); });
m_impl->m_jsRuntimeState = JsRuntime::CreateForJavaScript(env, [this](auto func) { Dispatch(std::move(func)); }).m_state;
Internal::DelayedTaskScheduler::SetForJavaScript(env, GetDelayedTaskScheduler());
m_impl->m_delayedTaskSchedulerRegistered = true;
});
Expand Down Expand Up @@ -94,6 +95,11 @@ namespace Babylon
}

Napi::HandleScope scope{env};

// Stop native completions before discarding work, while captures can still
// safely release environment-owned values. Do not rely on JS finalizer order.
JsRuntime::Close(m_impl->m_jsRuntimeState);

ShutdownEnvironment(env);

if (m_impl->m_delayedTaskSchedulerRegistered)
Expand Down
12 changes: 9 additions & 3 deletions Core/JsRuntime/Include/Babylon/JsRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include <Babylon/Api.h>

#include <functional>
#include <mutex>
#include <memory>

namespace Babylon
{
Expand Down Expand Up @@ -45,9 +45,15 @@ namespace Babylon
JsRuntime& operator=(const JsRuntime&) = delete;

private:
friend class AppRuntime;
friend class JsRuntimeScheduler;

JsRuntime(Napi::Env, DispatchFunctionT);
~JsRuntime();

static void Dispatch(const std::shared_ptr<InternalState>&, std::function<void BABYLON_API (Napi::Env)>);
static void Close(const std::shared_ptr<InternalState>&);

DispatchFunctionT m_dispatchFunction{};
std::mutex m_mutex{};
std::shared_ptr<InternalState> m_state;
};
}
24 changes: 20 additions & 4 deletions Core/JsRuntime/Include/Babylon/JsRuntimeScheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,45 @@

#include "JsRuntime.h"

#include <type_traits>
#include <utility>

namespace Babylon
{
/**
* Scheduler that invokes continuations via JsRuntime::Dispatch.
* Intended to be consumed by arcana.cpp tasks.
* Copies can outlive the runtime; dispatch after shutdown is discarded.
*/
class JsRuntimeScheduler
{
public:
explicit JsRuntimeScheduler(JsRuntime& runtime)
: m_runtime{runtime}
: m_runtimeState{runtime.m_state}
{
}

template<typename CallableT>
void operator()(CallableT&& callable) const
{
m_runtime.Dispatch([callable{std::forward<CallableT>(callable)}](Napi::Env) {
callable();
JsRuntime::Dispatch(m_runtimeState, [callable{std::forward<CallableT>(callable)}](Napi::Env env) mutable {
// Preserve the original const, zero-argument invocation when available.
if constexpr (std::is_invocable_v<const decltype(callable)&>)
{
std::as_const(callable)();
}
else if constexpr (std::is_invocable_v<decltype(callable)&>)
{
callable();
}
else
{
callable(env);
}
});
}

private:
JsRuntime& m_runtime;
std::shared_ptr<JsRuntime::InternalState> m_runtimeState;
};
}
48 changes: 45 additions & 3 deletions Core/JsRuntime/Source/JsRuntime.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
#include "JsRuntime.h"
#include "Babylon/DebugTrace.h"

#include <mutex>
#include <utility>

namespace Babylon
{
struct JsRuntime::InternalState
{
explicit InternalState(DispatchFunctionT dispatchFunction)
: DispatchFunction{std::move(dispatchFunction)}
{
}

DispatchFunctionT DispatchFunction;
std::mutex Mutex;
};

namespace
{
static constexpr auto JS_RUNTIME_NAME = "runtime";
static constexpr auto JS_WINDOW_NAME = "window";
}

JsRuntime::JsRuntime(Napi::Env env, DispatchFunctionT dispatchFunction)
: m_dispatchFunction{std::move(dispatchFunction)}
: m_state{std::make_shared<InternalState>(std::move(dispatchFunction))}
{
auto global = env.Global();

Expand All @@ -28,6 +42,22 @@ namespace Babylon
DEBUG_TRACE("JsRuntime created");
}

JsRuntime::~JsRuntime()
{
Close(m_state);
}

void JsRuntime::Close(const std::shared_ptr<InternalState>& state)
{
DispatchFunctionT dispatchFunction;
if (state)
{
std::scoped_lock lock{state->Mutex};
dispatchFunction = std::exchange(state->DispatchFunction, {});
}
// Captured objects may dispatch from their destructors. Release them unlocked.
}

JsRuntime& BABYLON_API JsRuntime::CreateForJavaScript(Napi::Env env, DispatchFunctionT dispatchFunction)
{
auto* runtime = new JsRuntime(env, std::move(dispatchFunction));
Expand All @@ -45,8 +75,20 @@ namespace Babylon

void JsRuntime::Dispatch(std::function<void BABYLON_API (Napi::Env)> function)
{
std::scoped_lock lock{m_mutex};
m_dispatchFunction([function = std::move(function)](Napi::Env env) {
Dispatch(m_state, std::move(function));
}

void JsRuntime::Dispatch(const std::shared_ptr<InternalState>& state, std::function<void BABYLON_API (Napi::Env)> function)
{
// Keep the host enqueue and Close mutually exclusive: copying the dispatch
// function out of the lock would allow it to run after the host is gone.
std::scoped_lock lock{state->Mutex};
if (!state->DispatchFunction)
{
return;
}

state->DispatchFunction([function = std::move(function)](Napi::Env env) {
function(env);

// The environment will be in a pending exceptional state if
Expand Down
1 change: 1 addition & 0 deletions Tests/UnitTests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ file(GLOB ASSETS "${CMAKE_CURRENT_SOURCE_DIR}/Assets/*")

set(SOURCES
"Shared/DelayedTaskScheduler.cpp"
"Shared/JsRuntimeScheduler.cpp"
"Shared/StandardStreamLogger.cpp"
"Shared/TimeoutDispatcher.cpp"
"Shared/Shared.cpp"
Expand Down
Loading
Loading