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
13 changes: 13 additions & 0 deletions Core/AppRuntime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ set(SOURCES
"Source/AppRuntime_${NAPI_JAVASCRIPT_ENGINE}.cpp"
"Source/AppRuntime_${JSRUNTIMEHOST_PLATFORM}.${IMPL_EXT}")

if(NAPI_JAVASCRIPT_ENGINE STREQUAL "V8")
list(APPEND SOURCES
"Source/V8ForegroundTaskRunner.h"
"Source/V8ForegroundTaskRunner.cpp"
"Source/V8Platform.h"
"Source/V8Platform.cpp")
endif()

add_library(AppRuntime ${SOURCES})
warnings_as_errors(AppRuntime)

Expand All @@ -19,6 +27,7 @@ target_include_directories(AppRuntime
INTERFACE "Include")

target_link_libraries(AppRuntime
PRIVATE FoundationInternal
PRIVATE arcana
PUBLIC JsRuntime)

Expand Down Expand Up @@ -58,3 +67,7 @@ endif()

set_property(TARGET AppRuntime PROPERTY FOLDER Core)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES})

add_library(AppRuntimeInternal INTERFACE)
target_include_directories(AppRuntimeInternal INTERFACE "Source")
target_link_libraries(AppRuntimeInternal INTERFACE AppRuntime INTERFACE FoundationInternal)
11 changes: 11 additions & 0 deletions Core/AppRuntime/Include/Babylon/AppRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@

namespace Babylon
{
namespace Internal
{
class DelayedTaskScheduler;
}

class AppRuntime final
{
public:
Expand Down Expand Up @@ -63,6 +68,10 @@ namespace Babylon
void RunEnvironmentTier(const char* executablePath = ".");
void Run(Napi::Env);

// Engine-specific hook to stop task routing before joining the scheduler
// and discarding queued work, while the environment is still attached.
void ShutdownEnvironment(Napi::Env env);

// This method is called from Dispatch to allow platform-specific code to add
// extra logic around the invocation of a dispatched callback.
void Execute(Dispatchable<void()> callback);
Expand All @@ -76,6 +85,8 @@ namespace Babylon
// queue explicitly (Napi::DrainJobs / JS_ExecutePendingJob).
void DrainMicrotasks(Napi::Env env);

Internal::DelayedTaskScheduler& GetDelayedTaskScheduler();

Options m_options;

class Impl;
Expand Down
21 changes: 21 additions & 0 deletions Core/AppRuntime/Source/AppRuntime.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "AppRuntime.h"

#include "DelayedTaskScheduler.h"

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

Expand Down Expand Up @@ -35,6 +37,8 @@ namespace Babylon
std::optional<std::scoped_lock<std::mutex>> m_suspensionLock{};
arcana::cancellation_source m_cancelSource{};
arcana::manual_dispatcher<128> m_dispatcher{};
std::unique_ptr<Internal::DelayedTaskScheduler> m_delayedTaskScheduler{std::make_unique<Internal::DelayedTaskScheduler>()};
bool m_delayedTaskSchedulerRegistered{};
std::thread m_thread;
};

Expand All @@ -51,6 +55,8 @@ namespace Babylon

Dispatch([this](Napi::Env env) {
JsRuntime::CreateForJavaScript(env, [this](auto func) { Dispatch(std::move(func)); });
Internal::DelayedTaskScheduler::SetForJavaScript(env, GetDelayedTaskScheduler());
m_impl->m_delayedTaskSchedulerRegistered = true;
});
}

Expand Down Expand Up @@ -87,10 +93,25 @@ namespace Babylon
m_impl->m_dispatcher.blocking_tick(m_impl->m_cancelSource);
}

Napi::HandleScope scope{env};
ShutdownEnvironment(env);

if (m_impl->m_delayedTaskSchedulerRegistered)
{
Internal::DelayedTaskScheduler::ClearFromJavaScript(env);
m_impl->m_delayedTaskSchedulerRegistered = false;
}
GetDelayedTaskScheduler().Shutdown();

// The dispatcher can be non-empty if something is dispatched after cancellation.
m_impl->m_dispatcher.clear();
}

Internal::DelayedTaskScheduler& AppRuntime::GetDelayedTaskScheduler()
{
return *m_impl->m_delayedTaskScheduler;
}

void AppRuntime::Suspend()
{
auto suspensionMutex = std::make_shared<std::mutex>();
Expand Down
4 changes: 4 additions & 0 deletions Core/AppRuntime/Source/AppRuntime_Chakra.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ namespace Babylon
Napi::Detach(env);
}

void AppRuntime::ShutdownEnvironment(Napi::Env)
{
}

void AppRuntime::DrainMicrotasks(Napi::Env)
{
// Chakra drains promise continuations through its
Expand Down
4 changes: 4 additions & 0 deletions Core/AppRuntime/Source/AppRuntime_Hermes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ namespace Babylon
Napi::Detach(env);
}

void AppRuntime::ShutdownEnvironment(Napi::Env)
{
}

void AppRuntime::DrainMicrotasks(Napi::Env env)
{
// Hermes does not auto-drain its job queue. Promise continuations,
Expand Down
4 changes: 4 additions & 0 deletions Core/AppRuntime/Source/AppRuntime_JSI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ namespace Babylon
Napi::Detach(env);
}

void AppRuntime::ShutdownEnvironment(Napi::Env)
{
}

void AppRuntime::DrainMicrotasks(Napi::Env)
{
// JSI/V8 backed JSI auto-drains microtasks per scope.
Expand Down
4 changes: 4 additions & 0 deletions Core/AppRuntime/Source/AppRuntime_JavaScriptCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ namespace Babylon
Napi::Detach(env);
}

void AppRuntime::ShutdownEnvironment(Napi::Env)
{
}

void AppRuntime::DrainMicrotasks(Napi::Env)
{
// JavaScriptCore drains microtasks automatically at script boundaries.
Expand Down
4 changes: 4 additions & 0 deletions Core/AppRuntime/Source/AppRuntime_QuickJS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ namespace Babylon
JS_FreeRuntime(runtime);
}

void AppRuntime::ShutdownEnvironment(Napi::Env)
{
}

void AppRuntime::DrainMicrotasks(Napi::Env env)
{
// QuickJS does not auto-drain its job queue. Promise continuations,
Expand Down
28 changes: 18 additions & 10 deletions Core/AppRuntime/Source/AppRuntime_V8.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "AppRuntime.h"
#include "V8Platform.h"
#include <napi/env.h>

#include <libplatform/libplatform.h>
Expand All @@ -7,7 +8,9 @@
#include <V8InspectorAgent.h>
#endif

#include <memory>
#include <optional>
#include <stdexcept>

namespace Babylon
{
Expand All @@ -20,7 +23,7 @@ namespace Babylon
{
v8::V8::InitializeICUDefaultLocation(executablePath);
v8::V8::InitializeExternalStartupData(executablePath);
m_platform = v8::platform::NewDefaultPlatform();
m_platform = std::make_unique<Internal::V8Platform>(v8::platform::NewDefaultPlatform());
v8::V8::InitializePlatform(m_platform.get());
v8::V8::Initialize();
}
Expand Down Expand Up @@ -49,13 +52,13 @@ namespace Babylon
return *s_module;
}

v8::Platform& Platform()
Internal::V8Platform& Platform()
{
return *m_platform;
}

private:
std::unique_ptr<v8::Platform> m_platform;
std::unique_ptr<Internal::V8Platform> m_platform;

static std::unique_ptr<Module> s_module;
};
Expand All @@ -65,14 +68,16 @@ namespace Babylon

void AppRuntime::RunEnvironmentTier(const char* executablePath)
{
// Create the isolate.
Module::Initialize(executablePath);
auto& platform = Module::Instance().Platform();

v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
v8::Isolate* isolate = v8::Isolate::Allocate();
// Initialization can post foreground work, so register before it starts.
platform.RegisterHost(isolate, *this, GetDelayedTaskScheduler());
v8::Isolate::Initialize(isolate, create_params);

// Use the isolate within a scope.
{
v8::Isolate::Scope isolate_scope{isolate};
v8::HandleScope isolate_handle_scope{isolate};
Expand All @@ -85,7 +90,7 @@ namespace Babylon
std::optional<V8InspectorAgent> agent;
if (m_options.EnableDebugger)
{
agent.emplace(Module::Instance().Platform(), isolate, context, "JsRuntimeHost");
agent.emplace(platform, isolate, context, "JsRuntimeHost");
agent->Start(5643, "JsRuntimeHost");

if (m_options.WaitForDebugger)
Expand All @@ -107,15 +112,18 @@ namespace Babylon
Napi::Detach(env);
}

// Destroy the isolate.
// todo : GetArrayBufferAllocator not available?
// delete isolate->GetArrayBufferAllocator();
isolate->Dispose();
}

void AppRuntime::ShutdownEnvironment(Napi::Env)
{
Module::Instance().Platform().UnregisterHost(v8::Isolate::GetCurrent());
}

void AppRuntime::DrainMicrotasks(Napi::Env)
{
// V8 auto-drains microtasks at the end of each script/callback when
// using the default MicrotasksPolicy. No explicit pump needed.
// V8 auto-drains microtasks. Foreground tasks run on AppRuntime's dispatcher.
}
}
126 changes: 126 additions & 0 deletions Core/AppRuntime/Source/V8ForegroundTaskRunner.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#include "V8ForegroundTaskRunner.h"

#include <algorithm>
#include <chrono>
#include <mutex>
#include <unordered_set>
#include <utility>

namespace Babylon::Internal
{
struct V8ForegroundTaskRunner::State
{
struct Pending
{
DelayedTaskScheduler::Id id{};
bool completed{};
};

State(DispatchFunction dispatch, ScheduleFunction schedule, CancelFunction cancel)
: dispatch{std::move(dispatch)}
, schedule{std::move(schedule)}
, cancel{std::move(cancel)}
{
}

// Scheduling may complete synchronously; dispatch must only enqueue work.
std::recursive_mutex mutex;
DispatchFunction dispatch;
ScheduleFunction schedule;
CancelFunction cancel;
std::unordered_set<std::shared_ptr<Pending>> pending;
};

V8ForegroundTaskRunner::V8ForegroundTaskRunner(DispatchFunction dispatch, ScheduleFunction schedule, CancelFunction cancel)
: m_state{std::make_shared<State>(std::move(dispatch), std::move(schedule), std::move(cancel))}
{
}

V8ForegroundTaskRunner::~V8ForegroundTaskRunner()
{
Shutdown();
}

void V8ForegroundTaskRunner::Shutdown()
{
std::scoped_lock lock{m_state->mutex};
m_state->dispatch = {};
auto pendingTasks = std::move(m_state->pending);
m_state->pending.clear();
for (const auto& pending : pendingTasks)
{
m_state->cancel(pending->id);
}
}

void V8ForegroundTaskRunner::PostTask(std::unique_ptr<v8::Task> task)
{
std::scoped_lock lock{m_state->mutex};
if (m_state->dispatch)
{
m_state->dispatch(std::move(task));
}
}

void V8ForegroundTaskRunner::PostNonNestableTask(std::unique_ptr<v8::Task> task)
{
PostTask(std::move(task));
}

DelayedTaskScheduler::TimePoint V8ForegroundTaskRunner::GetScheduledTime(std::chrono::steady_clock::time_point now, double delayInSeconds)
{
return std::chrono::ceil<DelayedTaskScheduler::TimePoint::duration>(
now + std::chrono::duration<double>{std::max(0.0, delayInSeconds)});
}

void V8ForegroundTaskRunner::PostDelayedTask(std::unique_ptr<v8::Task> task, double delayInSeconds)
{
const auto when = GetScheduledTime(std::chrono::steady_clock::now(), delayInSeconds);
const auto state = m_state;
std::scoped_lock lock{state->mutex};
if (!state->dispatch)
{
return;
}

const auto pending = std::make_shared<State::Pending>();
pending->id = state->schedule(when, [state, pending, task = std::shared_ptr<v8::Task>{std::move(task)}]() {
std::scoped_lock callbackLock{state->mutex};
pending->completed = true;
state->pending.erase(pending);
if (state->dispatch)
{
state->dispatch(task);
}
});
// A callback that fired before Schedule returned must not be reinserted.
if (!pending->completed)
{
state->pending.insert(pending);
}
}

void V8ForegroundTaskRunner::PostNonNestableDelayedTask(std::unique_ptr<v8::Task> task, double delayInSeconds)
{
PostDelayedTask(std::move(task), delayInSeconds);
}

void V8ForegroundTaskRunner::PostIdleTask(std::unique_ptr<v8::IdleTask>)
{
}

bool V8ForegroundTaskRunner::IdleTasksEnabled()
{
return false;
}

bool V8ForegroundTaskRunner::NonNestableTasksEnabled() const
{
return true;
}

bool V8ForegroundTaskRunner::NonNestableDelayedTasksEnabled() const
{
return true;
}
}
Loading
Loading