From e06b85c8977901e5393909271d7d4b4598db6905 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Thu, 9 Jul 2026 23:51:42 -0700 Subject: [PATCH] Multithread --- Sources/data_map.h | 69 +++++++ Sources/main.cpp | 89 ++++---- Sources/viewport_server.cpp | 16 +- Sources/worker.cpp | 395 ++++++++++++++++++++++++++++++------ 4 files changed, 459 insertions(+), 110 deletions(-) create mode 100644 Sources/data_map.h diff --git a/Sources/data_map.h b/Sources/data_map.h new file mode 100644 index 0000000..a2d5b59 --- /dev/null +++ b/Sources/data_map.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +struct DataMap { + void* data = nullptr; + size_t size = 0; + +#ifdef _WIN32 + HANDLE map_handle = nullptr; +#endif + + bool map_from_reader(void* reader_data, size_t reader_size) { + size = reader_size; + if (size == 0) return true; +#ifdef _WIN32 + HANDLE file_handle = reader_data; + map_handle = CreateFileMappingA(file_handle, nullptr, PAGE_READONLY, 0, 0, nullptr); + if (!map_handle) return false; + data = MapViewOfFile(map_handle, FILE_MAP_READ, 0, 0, 0); + if (!data) { + CloseHandle(map_handle); + map_handle = nullptr; + return false; + } + return true; +#else + int fd = fileno((FILE*)reader_data); + if (fd < 0) return false; + data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (data == MAP_FAILED) { + data = nullptr; + return false; + } + return true; +#endif + } + + void close() { + if (data) { +#ifdef _WIN32 + UnmapViewOfFile(data); +#else + munmap(data, size); +#endif + data = nullptr; + } +#ifdef _WIN32 + if (map_handle) { CloseHandle(map_handle); map_handle = nullptr; } +#endif + size = 0; + } + + ~DataMap() { close(); } +}; + +inline void delete_map(void* ptr, size_t length, void* deleter_data) { + DataMap* dm = static_cast(deleter_data); + if (dm) dm->close(); + delete dm; +} diff --git a/Sources/main.cpp b/Sources/main.cpp index 5ca5a9e..1856b0c 100644 --- a/Sources/main.cpp +++ b/Sources/main.cpp @@ -95,6 +95,7 @@ bool show_window = false; #endif #include "async_engine.h" +#include "data_map.h" // TODO //#ifdef KINC_KONG @@ -233,8 +234,14 @@ namespace { void handle_exception(TryCatch *try_catch) { MaybeLocal trace = try_catch->StackTrace(isolate->GetCurrentContext()); if (trace.IsEmpty()) { - String::Utf8Value stack_trace(isolate, try_catch->Message()->Get()); - write_stack_trace(*stack_trace); + MaybeLocal message = try_catch->Message(); + if (message.IsEmpty()) { + write_stack_trace("uncaught exception (no message available)"); + } + else { + String::Utf8Value stack_trace(isolate, message.ToLocalChecked()->Get()); + write_stack_trace(*stack_trace); + } } else { String::Utf8Value stack_trace(isolate, trace.ToLocalChecked()); @@ -1436,18 +1443,33 @@ namespace { Local cb = Local::Cast(args[1]); auto persistent_cb = std::make_shared>(isolate, cb); - EngineManager::AsyncEngine::instance().execute_io([filepath, persistent_cb]() { + (void)EngineManager::AsyncEngine::instance().execute_io([filepath, persistent_cb]() { kinc_file_reader_t reader; - bool success = kinc_file_reader_open(&reader, filepath.c_str(), KINC_FILE_TYPE_ASSET); - std::vector bytes; - if (success) { - uint32_t size = (uint32_t)kinc_file_reader_size(&reader); - bytes.resize(size); - kinc_file_reader_read(&reader, bytes.data(), size); - kinc_file_reader_close(&reader); + bool success = false; + bool reader_open = false; + DataMap* dm = nullptr; + + try { + success = kinc_file_reader_open(&reader, filepath.c_str(), KINC_FILE_TYPE_ASSET); + if (success) { + reader_open = true; + size_t size = kinc_file_reader_size(&reader); + dm = new DataMap(); + if (!dm->map_from_reader(reader.data, size)) { + delete dm; + dm = nullptr; + success = false; + } + kinc_file_reader_close(&reader); + reader_open = false; + } + } catch (...) { + success = false; + if (dm) { dm->close(); delete dm; dm = nullptr; } + if (reader_open) { kinc_file_reader_close(&reader); reader_open = false; } } - EngineManager::AsyncEngine::instance().push_event([persistent_cb, bytes = std::move(bytes), success]() { + EngineManager::AsyncEngine::instance().push_event([persistent_cb, dm, success]() { Locker locker{isolate}; Isolate::Scope isolate_scope(isolate); HandleScope handle_scope(isolate); @@ -1456,12 +1478,20 @@ namespace { TryCatch try_catch(isolate); Local cb = Local::New(isolate, *persistent_cb); - if (success && !bytes.empty()) { - Local buffer = ArrayBuffer::New(isolate, bytes.size()); - memcpy(buffer->GetBackingStore()->Data(), bytes.data(), bytes.size()); + if (success && dm) { + Local buffer; + if (dm->data) { + std::unique_ptr backing = + ArrayBuffer::NewBackingStore(dm->data, dm->size, delete_map, dm); + buffer = ArrayBuffer::New(isolate, std::move(backing)); + } else { + dm->close(); delete dm; + buffer = ArrayBuffer::New(isolate, 0); + } Local argv[1] = { buffer }; (void)cb->Call(context, context->Global(), 1, argv); } else { + if (dm) { dm->close(); delete dm; } Local argv[1] = { Null(isolate) }; (void)cb->Call(context, context->Global(), 1, argv); } @@ -3030,6 +3060,7 @@ namespace { Local navigator = Object::New(isolate); navigator->Set(context, String::NewFromUtf8(isolate, "userAgent").ToLocalChecked(), String::NewFromUtf8(isolate, "RunT/1.0").ToLocalChecked()); navigator->Set(context, String::NewFromUtf8(isolate, "platform").ToLocalChecked(), String::NewFromUtf8(isolate, "RunT").ToLocalChecked()); + navigator->Set(context, String::NewFromUtf8(isolate, "hardwareConcurrency").ToLocalChecked(), Integer::New(isolate, kinc_hardware_threads())); window->Set(context, String::NewFromUtf8(isolate, "navigator").ToLocalChecked(), navigator); Local document = Object::New(isolate); @@ -3128,6 +3159,7 @@ namespace { #ifdef WITH_WORKER handle_worker_messages(isolate, global_context); + MicrotasksScope::PerformCheckpoint(isolate); #endif #ifdef WITH_NETWORKING @@ -4038,6 +4070,14 @@ int kickstart(int argc, char **argv) { start_runt(snapshot_found ? NULL : code); + // TODO: find and fix + while (update_func.IsEmpty()) { + handle_worker_messages(isolate, global_context); + EngineManager::AsyncEngine::instance().process_events(); + MicrotasksScope::PerformCheckpoint(isolate); + kinc_thread_sleep(1); + } + #ifdef WITH_VIEWPORT if (viewport_server_mode) { kinc_log(KINC_LOG_LEVEL_INFO, "Initializing viewport server: %dx%d, shmem=%s", @@ -4052,27 +4092,6 @@ int kickstart(int argc, char **argv) { kinc_start(); - // NOTE: WebSocket benchmark disabled for faster bootup - // #ifdef WITH_NETWORKING - // kinc_log(KINC_LOG_LEVEL_INFO, "=== WEBSOCKET BOOTUP BENCHMARK ==="); - // { - // WebSocketBenchmark benchmark; - // kinc_log(KINC_LOG_LEVEL_INFO, "Running quick WebSocket performance check..."); - // auto result = benchmark.runCollaborative3DTest(5, 10); - // kinc_log(KINC_LOG_LEVEL_INFO, "Benchmark Results:"); - // kinc_log(KINC_LOG_LEVEL_INFO, "- Messages/sec: %llu", result.messagesPerSecond); - // kinc_log(KINC_LOG_LEVEL_INFO, "- Avg Latency: %.1f μs", result.averageLatency); - // kinc_log(KINC_LOG_LEVEL_INFO, "- CPU Usage: %.1f%%", result.avgCpuPercent); - // kinc_log(KINC_LOG_LEVEL_INFO, "- Memory: %zu MB", result.peakMemoryMB); - // if (result.messagesPerSecond > 100) { - // kinc_log(KINC_LOG_LEVEL_INFO, "✓ WebSocket performance: GOOD"); - // } else { - // kinc_log(KINC_LOG_LEVEL_WARNING, "⚠ WebSocket performance: LOW - check system load"); - // } - // } - // kinc_log(KINC_LOG_LEVEL_INFO, "=== BENCHMARK COMPLETE ==="); - // #endif - #ifdef WITH_AUDIO if (enable_audio) { kinc_a2_shutdown(); diff --git a/Sources/viewport_server.cpp b/Sources/viewport_server.cpp index 3c7f183..d77cfac 100644 --- a/Sources/viewport_server.cpp +++ b/Sources/viewport_server.cpp @@ -562,16 +562,16 @@ void viewport_server_set_camera(float pos_x, float pos_y, float pos_z, SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr; if (!header) return; - header->krom_camera_pos[0] = pos_x; - header->krom_camera_pos[1] = pos_y; - header->krom_camera_pos[2] = pos_z; + header->runt_camera_pos[0] = pos_x; + header->runt_camera_pos[1] = pos_y; + header->runt_camera_pos[2] = pos_z; - header->krom_camera_rot[0] = rot_x; - header->krom_camera_rot[1] = rot_y; - header->krom_camera_rot[2] = rot_z; - header->krom_camera_rot[3] = rot_w; + header->runt_camera_rot[0] = rot_x; + header->runt_camera_rot[1] = rot_y; + header->runt_camera_rot[2] = rot_z; + header->runt_camera_rot[3] = rot_w; - header->krom_camera_dirty = 1; + header->runt_camera_dirty = 1; } bool viewport_server_read_input(InputEvent* event) { diff --git a/Sources/worker.cpp b/Sources/worker.cpp index 269b80b..aa10f58 100644 --- a/Sources/worker.cpp +++ b/Sources/worker.cpp @@ -8,30 +8,42 @@ #include #include #include +#include #include #include +#include +#include +#include +#include +#include + using namespace v8; namespace { const uint32_t worker_data_slot = 0; + struct TransferData; + struct WorkerMessage { - char* message; - int length; + uint8_t* data; + size_t size; + TransferData* transferData; }; struct MessageQueue { std::vector messages; kinc_mutex_t messageMutex; + kinc_event_t event; Global messageFunc; + const char* label = "?"; }; struct WorkerMessagePort { MessageQueue workerMessages; MessageQueue ownerMessages; - bool isTerminated = false; + std::atomic isTerminated{false}; }; struct WorkerData { @@ -53,6 +65,7 @@ namespace { double interval; double nextCallTime; int id; + bool isTimeout; }; struct IntervalFunctions { @@ -60,6 +73,70 @@ namespace { int latestId; }; + struct TransferData { + std::vector> sharedArrayBuffers; + std::vector wasmModules; + std::unique_ptr sharedValueDispatch; + }; + + class SerializerDelegate : public ValueSerializer::Delegate { + public: + TransferData* transferData; + + SerializerDelegate(TransferData* data) : transferData(data) {} + + void ThrowDataCloneError(Local message) override { + Isolate* isolate = Isolate::GetCurrent(); + isolate->ThrowException(message); + } + + Maybe GetSharedArrayBufferId(Isolate* isolate, Local shared_array_buffer) override { + auto backingStore = shared_array_buffer->GetBackingStore(); + uint32_t id = (uint32_t)transferData->sharedArrayBuffers.size(); + transferData->sharedArrayBuffers.push_back(backingStore); + return Just(id); + } + + Maybe GetWasmModuleTransferId(Isolate* isolate, Local module) override { + CompiledWasmModule compiled = module->GetCompiledModule(); + uint32_t id = (uint32_t)transferData->wasmModules.size(); + transferData->wasmModules.push_back(compiled); + return Just(id); + } + + bool AdoptSharedValueConveyor(Isolate* isolate, SharedValueConveyor&& conveyor) override { + transferData->sharedValueDispatch = std::make_unique(std::move(conveyor)); + return true; + } + }; + + class DeserializerDelegate : public ValueDeserializer::Delegate { + public: + TransferData* transferData; + + DeserializerDelegate(TransferData* data) : transferData(data) {} + + MaybeLocal GetSharedArrayBufferFromId(Isolate* isolate, uint32_t clone_id) override { + if (clone_id >= transferData->sharedArrayBuffers.size()) { + return MaybeLocal(); + } + return SharedArrayBuffer::New(isolate, transferData->sharedArrayBuffers[clone_id]); + } + + MaybeLocal GetWasmModuleFromId(Isolate* isolate, uint32_t transfer_id) override { + if (transfer_id >= transferData->wasmModules.size()) { + return MaybeLocal(); + } + return WasmModuleObject::FromCompiledModule(isolate, transferData->wasmModules[transfer_id]); + } + + const SharedValueConveyor* GetSharedValueConveyor(Isolate* isolate) override { + return transferData->sharedValueDispatch.get(); + } + }; + + void handle_exception(Isolate* isolate, TryCatch* try_catch); + void set_onmessage(MessageQueue* messageQueue, Isolate* isolate, Local callback) { if (callback->IsFunction()) { messageQueue->messageFunc.Reset(isolate, Local::Cast(callback)); @@ -73,20 +150,53 @@ namespace { } void post_message(MessageQueue* messageQueue, Isolate* isolate, Local messageObject) { - Local result = JSON::Stringify(isolate->GetCurrentContext(), messageObject).ToLocalChecked(); + Local context = isolate->GetCurrentContext(); + TransferData* transferData = new TransferData(); + + SerializerDelegate delegate(transferData); + ValueSerializer serializer(isolate, &delegate); + serializer.WriteHeader(); + + TryCatch try_catch(isolate); + Maybe writeResult = serializer.WriteValue(context, messageObject); + if (writeResult.IsNothing()) { + if (try_catch.HasCaught()) { + isolate->ThrowException(try_catch.Exception()); + } else { + isolate->ThrowException(String::NewFromUtf8(isolate, "postMessage: failed to serialize message").ToLocalChecked()); + } + delete transferData; + return; + } + + std::pair released = serializer.Release(); WorkerMessage message; - message.length = result->Length(); - message.message = new char[message.length]; - result->WriteUtf8(isolate, message.message, message.length); + message.data = released.first; + message.size = released.second; + message.transferData = transferData; kinc_mutex_lock(&messageQueue->messageMutex); messageQueue->messages.push_back(message); kinc_mutex_unlock(&messageQueue->messageMutex); + kinc_event_signal(&messageQueue->event); } void handle_message_queue(Isolate* isolate, const Global& context, MessageQueue* messageQueue) { + kinc_mutex_lock(&messageQueue->messageMutex); + if (messageQueue->messages.empty()) { + kinc_mutex_unlock(&messageQueue->messageMutex); + return; + } + std::vector messages; + messages.swap(messageQueue->messages); + kinc_mutex_unlock(&messageQueue->messageMutex); + if (messageQueue->messageFunc.IsEmpty()) { + for (WorkerMessage& message : messages) { + free(message.data); + delete message.transferData; + } return; } @@ -94,29 +204,63 @@ namespace { Local current_context = Local::New(isolate, context); Context::Scope context_scope(current_context); - kinc_mutex_lock(&messageQueue->messageMutex); + for (WorkerMessage& message : messages) { + DeserializerDelegate delegate(message.transferData); + ValueDeserializer deserializer(isolate, message.data, message.size, &delegate); + + Maybe headerResult = deserializer.ReadHeader(current_context); + if (headerResult.IsNothing()) { + free(message.data); + delete message.transferData; + continue; + } + + MaybeLocal value = deserializer.ReadValue(current_context); + + free(message.data); + + if (value.IsEmpty()) { + delete message.transferData; + continue; + } - for (WorkerMessage& message : messageQueue->messages) { - MaybeLocal value = JSON::Parse(current_context, String::NewFromUtf8(isolate, message.message, NewStringType::kNormal, message.length).ToLocalChecked()); - delete[] message.message; Local object = Object::New(isolate); object->Set(current_context, String::NewFromUtf8(isolate, "data").ToLocalChecked(), value.ToLocalChecked()); Local args[] = { object }; Local message_func = messageQueue->messageFunc.Get(isolate); - message_func->Call(current_context, current_context->Global(), 1, args); + TryCatch try_catch(isolate); + Local callResult; + if (!message_func->Call(current_context, current_context->Global(), 1, args).ToLocal(&callResult)) { + handle_exception(isolate, &try_catch); + } + delete message.transferData; } + } - messageQueue->messages.clear(); - - kinc_mutex_unlock(&messageQueue->messageMutex); + void worker_console_log(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope scope(isolate); + std::string output; + for (int i = 0; i < args.Length(); ++i) { + if (i > 0) output += " "; + String::Utf8Value utf8(isolate, args[i]); + output += *utf8; + } + kinc_log(KINC_LOG_LEVEL_INFO, "%s", output.c_str()); } void handle_exception(Isolate* isolate, TryCatch* try_catch) { MaybeLocal trace = try_catch->StackTrace(isolate->GetCurrentContext()); if (trace.IsEmpty()) { - v8::String::Utf8Value stack_trace(isolate, try_catch->Message()->Get()); - kinc_log(KINC_LOG_LEVEL_ERROR, "uncaught exception %s", *stack_trace); + MaybeLocal message = try_catch->Message(); + if (message.IsEmpty()) { + kinc_log(KINC_LOG_LEVEL_ERROR, "uncaught exception (no message available)"); + } + else { + v8::String::Utf8Value stack_trace(isolate, message.ToLocalChecked()->Get()); + kinc_log(KINC_LOG_LEVEL_ERROR, "uncaught exception %s", *stack_trace); + } } else { v8::String::Utf8Value stack_trace(isolate, trace.ToLocalChecked()); @@ -130,9 +274,6 @@ namespace { if (args.Length() < 1) { return; } - if (args.Length() > 1) { - kinc_log(KINC_LOG_LEVEL_WARNING, "RunT workers only support 1 argument for postMessage"); - } Local external = Local::Cast(args.Data()); WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value(); @@ -182,41 +323,60 @@ namespace { set_onmessage(&messagePort->ownerMessages, isolate, args[1]); } - void worker_set_interval(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - HandleScope scope(isolate); - - Local external = Local::Cast(args.Data()); - IntervalFunctions* interval_functions = (IntervalFunctions*)external->Value(); - + int add_timer(IntervalFunctions* interval_functions, Isolate* isolate, const FunctionCallbackInfo& args, bool is_timeout) { + if (args.Length() < 1 || !args[0]->IsFunction()) { + isolate->ThrowException(String::NewFromUtf8(isolate, "Timer callback must be a function").ToLocalChecked()); + return -1; + } Local function = Local::Cast(args[0]); - double frequency; + double delay; if (args.Length() > 1) { - frequency = Local::Cast(args[1])->Value(); - frequency /= 1000.0; + if (!args[1]->IsNumber()) { + isolate->ThrowException(String::NewFromUtf8(isolate, "Timer delay must be a number").ToLocalChecked()); + return -1; + } + delay = Local::Cast(args[1])->Value() / 1000.0; } else { - frequency = 0.016; + delay = is_timeout ? 0.0 : 0.016; } interval_functions->functions.emplace_back(); - IntervalFunction& interval_function = interval_functions->functions[interval_functions->functions.size() - 1]; + IntervalFunction& interval_function = interval_functions->functions.back(); interval_function.function.Reset(isolate, function); - interval_function.interval = frequency; - interval_function.nextCallTime = kinc_time() + frequency; + interval_function.interval = delay; + interval_function.nextCallTime = kinc_time() + delay; interval_function.id = interval_functions->latestId; + interval_function.isTimeout = is_timeout; interval_functions->latestId++; - - args.GetReturnValue().Set(interval_function.id); + return interval_function.id; } - void worker_clear_interval(const FunctionCallbackInfo& args) { + void worker_set_interval(const FunctionCallbackInfo& args) { + HandleScope scope(args.GetIsolate()); + IntervalFunctions* interval_functions = (IntervalFunctions*)Local::Cast(args.Data())->Value(); + args.GetReturnValue().Set(add_timer(interval_functions, args.GetIsolate(), args, false)); + } + + void worker_set_timeout(const FunctionCallbackInfo& args) { + HandleScope scope(args.GetIsolate()); + IntervalFunctions* interval_functions = (IntervalFunctions*)Local::Cast(args.Data())->Value(); + args.GetReturnValue().Set(add_timer(interval_functions, args.GetIsolate(), args, true)); + } + + void worker_clear_timer(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope scope(isolate); + if (args.Length() < 1 || !args[0]->IsInt32()) { + return; + } + Local external = Local::Cast(args.Data()); IntervalFunctions* intervalFunctions = (IntervalFunctions*)external->Value(); - int id = Local::Cast(args[0])->Value(); + int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked(); for (std::vector::iterator it = intervalFunctions->functions.begin(); it != intervalFunctions->functions.end(); ++it) { if (it->id == id) { @@ -232,8 +392,10 @@ namespace { WorkerMessagePort* message_port = worker_data->messagePort; Isolate::CreateParams create_params; - create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); + auto array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); + create_params.array_buffer_allocator = array_buffer_allocator; Isolate* isolate = Isolate::New(create_params); + { Locker locker(isolate); Isolate::Scope isolate_scope(isolate); HandleScope handle_scope(isolate); @@ -250,13 +412,34 @@ namespace { Local extern_interval_functions = External::New(isolate, &interval_functions); global->Set(String::NewFromUtf8(isolate, "setInterval").ToLocalChecked(), FunctionTemplate::New(isolate, worker_set_interval, extern_interval_functions)); - global->Set(String::NewFromUtf8(isolate, "clearInterval").ToLocalChecked(), FunctionTemplate::New(isolate, worker_clear_interval, extern_interval_functions)); + global->Set(String::NewFromUtf8(isolate, "clearInterval").ToLocalChecked(), FunctionTemplate::New(isolate, worker_clear_timer, extern_interval_functions)); + global->Set(String::NewFromUtf8(isolate, "setTimeout").ToLocalChecked(), FunctionTemplate::New(isolate, worker_set_timeout, extern_interval_functions)); + global->Set(String::NewFromUtf8(isolate, "clearTimeout").ToLocalChecked(), FunctionTemplate::New(isolate, worker_clear_timer, extern_interval_functions)); + + Local console_template = ObjectTemplate::New(isolate); + console_template->Set(String::NewFromUtf8(isolate, "log").ToLocalChecked(), FunctionTemplate::New(isolate, worker_console_log)); + console_template->Set(String::NewFromUtf8(isolate, "warn").ToLocalChecked(), FunctionTemplate::New(isolate, worker_console_log)); + console_template->Set(String::NewFromUtf8(isolate, "error").ToLocalChecked(), FunctionTemplate::New(isolate, worker_console_log)); + console_template->Set(String::NewFromUtf8(isolate, "info").ToLocalChecked(), FunctionTemplate::New(isolate, worker_console_log)); + console_template->Set(String::NewFromUtf8(isolate, "debug").ToLocalChecked(), FunctionTemplate::New(isolate, worker_console_log)); + global->Set(String::NewFromUtf8(isolate, "console").ToLocalChecked(), console_template); Local context = Context::New(isolate, nullptr, global); Global global_context; global_context.Reset(isolate, context); Context::Scope context_scope(context); + Local global_obj = context->Global(); + global_obj->Set(context, String::NewFromUtf8(isolate, "self").ToLocalChecked(), global_obj).ToChecked(); + global_obj->Set(context, String::NewFromUtf8(isolate, "WorkerGlobalScope").ToLocalChecked(), global_obj).ToChecked(); + global_obj->Set(context, String::NewFromUtf8(isolate, "globalThis").ToLocalChecked(), global_obj).ToChecked(); + + Local navigator = Object::New(isolate); + navigator->Set(context, String::NewFromUtf8(isolate, "hardwareConcurrency").ToLocalChecked(), Integer::New(isolate, kinc_hardware_threads())).ToChecked(); + navigator->Set(context, String::NewFromUtf8(isolate, "userAgent").ToLocalChecked(), String::NewFromUtf8(isolate, "RunT/1.0").ToLocalChecked()).ToChecked(); + navigator->Set(context, String::NewFromUtf8(isolate, "language").ToLocalChecked(), String::NewFromUtf8(isolate, "en-US").ToLocalChecked()).ToChecked(); + global_obj->Set(context, String::NewFromUtf8(isolate, "navigator").ToLocalChecked(), navigator).ToChecked(); + bind_worker_class(isolate, global_context); kinc_file_reader_t reader; @@ -274,46 +457,105 @@ namespace { Local source = String::NewFromUtf8(isolate, code, NewStringType::kNormal).ToLocalChecked(); TryCatch try_catch(isolate); - Local