Merge pull request 'Here comes the RunT!' (#1) from Onek8/LNXCORE:main into main

Reviewed-on: LeenkxTeam/LNXCORE#1
This commit is contained in:
2026-02-21 07:45:33 +00:00
36 changed files with 11508 additions and 416 deletions

View File

@ -29,6 +29,7 @@ struct linux_procs {
int (*window_display)(int window_index);
void (*window_show)(int window_index);
void (*window_hide)(int window_index);
void (*window_set_foreground)(int window_index);
void (*window_set_title)(int window_index, const char *title);
void (*window_change_mode)(int window_index, kinc_window_mode_t mode);
kinc_window_mode_t (*window_get_mode)(int window_index);

View File

@ -59,6 +59,7 @@ void kinc_linux_init_procs() {
procs.window_resize = kinc_wayland_window_resize;
procs.window_show = kinc_wayland_window_show;
procs.window_hide = kinc_wayland_window_hide;
procs.window_set_foreground = kinc_wayland_window_set_foreground;
procs.count_windows = kinc_wayland_count_windows;
procs.mouse_can_lock = kinc_wl_mouse_can_lock;
@ -111,6 +112,8 @@ void kinc_linux_init_procs() {
procs.window_resize = kinc_x11_window_resize;
procs.window_show = kinc_x11_window_show;
procs.window_hide = kinc_x11_window_hide;
procs.window_set_foreground = kinc_x11_window_set_foreground;
procs.count_windows = kinc_x11_count_windows;
procs.display_init = kinc_x11_display_init;

View File

@ -80,6 +80,10 @@ void kinc_window_hide(int window_index) {
procs.window_hide(window_index);
}
void kinc_window_set_foreground(int window_index) {
procs.window_set_foreground(window_index);
}
void kinc_window_set_title(int window_index, const char *title) {
procs.window_set_title(window_index, title);
}

View File

@ -145,6 +145,16 @@ void kinc_x11_window_hide(int window_index) {
xlib.XUnmapWindow(x11_ctx.display, window->window);
}
void kinc_x11_window_set_foreground(int window_index) {
struct kinc_x11_window *window = &x11_ctx.windows[window_index];
if (window->window == None) {
return;
}
xlib.XRaiseWindow(x11_ctx.display, window->window);
xlib.XSetInputFocus(x11_ctx.display, window->window, RevertToParent, CurrentTime);
xlib.XFlush(x11_ctx.display);
}
kinc_window_mode_t kinc_x11_window_get_mode(int window_index) {
return x11_ctx.windows[window_index].mode;
}

View File

@ -392,6 +392,11 @@ void kinc_window_hide(int window_index) {
UpdateWindow(windows[window_index].handle);
}
void kinc_window_set_foreground(int window_index) {
SetForegroundWindow(windows[window_index].handle);
SetFocus(windows[window_index].handle);
}
void kinc_window_set_title(int window_index, const char *title) {
wchar_t buffer[1024];
MultiByteToWideChar(CP_UTF8, 0, title, -1, buffer, 1024);

View File

@ -139,6 +139,11 @@ KINC_FUNC void kinc_window_show(int window);
/// </summary>
KINC_FUNC void kinc_window_hide(int window);
/// <summary>
/// Set a window to the foreground.
/// </summary>
KINC_FUNC void kinc_window_set_foreground(int window);
/// <summary>
/// Sets the title of a window.
/// </summary>

View File

@ -1,19 +1,20 @@
# LNXCORE
# LNXRNT
LeenkxCore for Leenkx Full Stack SDK.
Leenkx RunT - Runtime for Leenkx Full Stack SDK.
Based on [Krom](https://github.com/Kode/Krom). Powered by [Kinc](https://github.com/Kode/Kinc).
```bash
git clone --recursive https://dev.leenkx.com/LeenkxTeam/LNXCORE
cd LNXCORE
git clone --recursive https://dev.leenkx.com/LeenkxTeam/LNXRNT
cd LNXRNT
```
**Windows**
```bash
# Unpack `v8\libraries\win32\release\v8_monolith.7z` using 7-Zip - Extract Here (exceeds 100MB)
Kinc/make -g direct3d11
# Open generated Visual Studio project at `build\Krom.sln`
Kinc\make -g direct3d11
# Open generated Visual Studio project at `build\RunT.sln`
# or use command line for solution file like C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe" "build\Leenkx-RunT.sln" /p:Configuration=Release /p:Platform=x64 /m
# Build for x64 & release
```
@ -21,12 +22,12 @@ Kinc/make -g direct3d11
```bash
Kinc/make -g opengl --compiler clang --compile
cd Deployment
strip Krom
strip RunT
```
**macOS**
```bash
Kinc/make -g metal
# Open generated Xcode project at `build/Krom.xcodeproj`
# Open generated Xcode project at `build/RunT.xcodeproj`
# Build
```

9
Sources/async_engine.cpp Normal file
View File

@ -0,0 +1,9 @@
#include "async_engine.h"
#include <kinc/log.h>
namespace EngineManager {
std::unique_ptr<AsyncEngine> AsyncEngine::instance_;
std::once_flag AsyncEngine::initialized_;
}

199
Sources/async_engine.h Normal file
View File

@ -0,0 +1,199 @@
#pragma once
#include <atomic>
#include <memory>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>
namespace EngineManager {
// thread communication
template<typename T>
class LockFreeQueue {
private:
struct Node {
std::atomic<T*> data{nullptr};
std::atomic<Node*> next{nullptr};
};
std::atomic<Node*> head_{nullptr};
std::atomic<Node*> tail_{nullptr};
public:
LockFreeQueue() {
Node* dummy = new Node;
head_.store(dummy);
tail_.store(dummy);
}
~LockFreeQueue() {
while (Node* const old_head = head_.load()) {
head_.store(old_head->next);
delete old_head->data.load();
delete old_head;
}
}
void push(T item) {
Node* new_node = new Node;
T* data = new T(std::move(item));
new_node->data.store(data);
Node* prev_tail = tail_.exchange(new_node);
prev_tail->next.store(new_node);
}
bool try_pop(T& result) {
Node* head = head_.load();
Node* next = head->next.load();
if (next == nullptr) return false;
T* data = next->data.load();
if (data == nullptr) return false;
result = *data;
delete data;
head_.store(next);
delete head;
return true;
}
bool empty() const {
Node* head = head_.load();
return (head->next.load() == nullptr);
}
};
// thread pool stealing
class ThreadPool {
private:
std::vector<std::thread> workers_;
std::queue<std::function<void()>> tasks_;
std::mutex queue_mutex_;
std::condition_variable condition_;
std::atomic<bool> stop_{false};
public:
explicit ThreadPool(size_t num_threads = std::thread::hardware_concurrency()) {
for (size_t i = 0; i < num_threads; ++i) {
workers_.emplace_back([this] {
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queue_mutex_);
condition_.wait(lock, [this] { return stop_ || !tasks_.empty(); });
if (stop_ && tasks_.empty()) return;
task = std::move(tasks_.front());
tasks_.pop();
}
task();
}
});
}
}
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args) -> std::future<typename std::invoke_result<F, Args...>::type> {
using return_type = typename std::invoke_result<F, Args...>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex_);
if (stop_) {
throw std::runtime_error("ThreadPool: enqueue stopped");
}
tasks_.emplace([task](){ (*task)(); });
}
condition_.notify_one();
return res;
}
size_t pending_tasks() const {
std::lock_guard<std::mutex> lock(const_cast<std::mutex&>(queue_mutex_));
return tasks_.size();
}
~ThreadPool() {
stop_ = true;
condition_.notify_all();
for (std::thread& worker : workers_) {
worker.join();
}
}
};
class AsyncEngine {
private:
static std::unique_ptr<AsyncEngine> instance_;
static std::once_flag initialized_;
std::unique_ptr<ThreadPool> main_pool_;
std::unique_ptr<ThreadPool> io_pool_;
LockFreeQueue<std::function<void()>> event_queue_;
AsyncEngine() {
unsigned int hw_threads = std::thread::hardware_concurrency();
size_t main_threads = hw_threads > 4 ? hw_threads / 2 : 2;
size_t io_threads = hw_threads > 8 ? 4 : 2;
main_pool_ = std::make_unique<ThreadPool>(main_threads);
io_pool_ = std::make_unique<ThreadPool>(io_threads);
}
public:
static AsyncEngine& instance() {
std::call_once(initialized_, []() {
instance_ = std::unique_ptr<AsyncEngine>(new AsyncEngine());
});
return *instance_;
}
// main
template<class F, class... Args>
auto execute(F&& f, Args&&... args) -> std::future<typename std::invoke_result<F, Args...>::type> {
return main_pool_->enqueue(std::forward<F>(f), std::forward<Args>(args)...);
}
template<class F, class... Args>
auto execute_io(F&& f, Args&&... args) -> std::future<typename std::invoke_result<F, Args...>::type> {
return io_pool_->enqueue(std::forward<F>(f), std::forward<Args>(args)...);
}
void push_event(std::function<void()> event) {
event_queue_.push(std::move(event));
}
void process_events() {
std::function<void()> event;
while (event_queue_.try_pop(event)) {
event();
}
}
size_t pending_main_tasks() const { return main_pool_->pending_tasks(); }
size_t pending_io_tasks() const { return io_pool_->pending_tasks(); }
static void shutdown() {
instance_.reset();
}
};
}

75
Sources/broadcast_queue.h Normal file
View File

@ -0,0 +1,75 @@
#pragma once
#include <atomic>
#include <vector>
#include <memory>
// message queue
struct BroadcastMessage {
std::vector<uint8_t> frameData;
int excludeClientId;
BroadcastMessage() = default;
BroadcastMessage(std::vector<uint8_t> data, int excludeId)
: frameData(std::move(data)), excludeClientId(excludeId) {}
};
class LockFreeBroadcastQueue {
private:
static const size_t QUEUE_SIZE = 65536; // Must be power of 2
static const size_t QUEUE_MASK = QUEUE_SIZE - 1;
struct alignas(64) QueueSlot {
std::atomic<bool> ready{false};
BroadcastMessage message;
};
alignas(64) std::atomic<size_t> head{0};
alignas(64) std::atomic<size_t> tail{0};
QueueSlot queue[QUEUE_SIZE];
public:
bool push(BroadcastMessage&& message) {
const size_t currentTail = tail.load(std::memory_order_relaxed);
const size_t nextTail = (currentTail + 1) & QUEUE_MASK;
// queue full
if (nextTail == head.load(std::memory_order_acquire)) {
return false;
}
QueueSlot& slot = queue[currentTail];
slot.message = std::move(message);
slot.ready.store(true, std::memory_order_release);
tail.store(nextTail, std::memory_order_release);
return true;
}
bool pop(BroadcastMessage& message) {
const size_t currentHead = head.load(std::memory_order_relaxed);
// empty
if (currentHead == tail.load(std::memory_order_acquire)) {
return false;
}
QueueSlot& slot = queue[currentHead];
if (!slot.ready.load(std::memory_order_acquire)) {
return false;
}
message = std::move(slot.message);
slot.ready.store(false, std::memory_order_release);
head.store((currentHead + 1) & QUEUE_MASK, std::memory_order_release);
return true;
}
size_t size() const {
return (tail.load(std::memory_order_relaxed) - head.load(std::memory_order_relaxed)) & QUEUE_MASK;
}
bool empty() const {
return head.load(std::memory_order_relaxed) == tail.load(std::memory_order_relaxed);
}
};

181
Sources/connection_pool.cpp Normal file
View File

@ -0,0 +1,181 @@
#include "connection_pool.h"
#include "socket_optimization.h"
#include <kinc/log.h>
#ifdef _WIN32
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#endif
// global
ConnectionPool g_connection_pool;
ConnectionPool::~ConnectionPool() {
std::lock_guard<std::mutex> lock(pool_mutex_);
for (auto& host_pair : pool_) {
while (!host_pair.second.empty()) {
auto conn = host_pair.second.front();
host_pair.second.pop();
#ifdef _WIN32
closesocket(conn.socket);
#else
close(conn.socket);
#endif
}
}
}
socket_t ConnectionPool::acquire_connection(const std::string& host, int port, bool is_ssl) {
std::string key = make_key(host, port, is_ssl);
{
std::lock_guard<std::mutex> lock(pool_mutex_);
auto it = pool_.find(key);
if (it != pool_.end() && !it->second.empty()) {
auto conn = it->second.front();
it->second.pop();
if (is_socket_alive(conn.socket)) {
kinc_log(KINC_LOG_LEVEL_INFO, "Reusing pooled connection for %s:%d", host.c_str(), port);
return conn.socket;
} else {
#ifdef _WIN32
closesocket(conn.socket);
#else
close(conn.socket);
#endif
}
}
}
socket_t sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create socket for %s:%d", host.c_str(), port);
return INVALID_SOCKET;
}
SocketOptimization::optimizeHttp(static_cast<int>(sock));
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
struct hostent* host_entry = gethostbyname(host.c_str());
if (!host_entry) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to resolve hostname %s", host.c_str());
#ifdef _WIN32
closesocket(sock);
#else
close(sock);
#endif
return INVALID_SOCKET;
}
memcpy(&server_addr.sin_addr, host_entry->h_addr, host_entry->h_length);
if (::connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to connect to %s:%d", host.c_str(), port);
#ifdef _WIN32
closesocket(sock);
#else
close(sock);
#endif
return INVALID_SOCKET;
}
kinc_log(KINC_LOG_LEVEL_INFO, "Created new connection for %s:%d", host.c_str(), port);
return sock;
}
void ConnectionPool::release_connection(const std::string& host, int port, socket_t socket, bool is_ssl) {
if (socket == INVALID_SOCKET) return;
std::string key = make_key(host, port, is_ssl);
std::lock_guard<std::mutex> lock(pool_mutex_);
auto& queue = pool_[key];
if (queue.size() >= MAX_CONNECTIONS_PER_HOST) {
#ifdef _WIN32
closesocket(socket);
#else
close(socket);
#endif
return;
}
PooledConnection conn;
conn.socket = socket;
conn.last_used = std::chrono::steady_clock::now();
conn.is_ssl = is_ssl;
queue.push(conn);
kinc_log(KINC_LOG_LEVEL_INFO, "Pooled connection for %s:%d", host.c_str(), port);
}
void ConnectionPool::cleanup_expired() {
std::lock_guard<std::mutex> lock(pool_mutex_);
auto now = std::chrono::steady_clock::now();
for (auto& host_pair : pool_) {
auto& queue = host_pair.second;
std::queue<PooledConnection> new_queue;
while (!queue.empty()) {
auto conn = queue.front();
queue.pop();
auto age = std::chrono::duration_cast<std::chrono::seconds>(now - conn.last_used);
if (age.count() < CONNECTION_TIMEOUT_SECONDS && is_socket_alive(conn.socket)) {
new_queue.push(conn);
} else {
#ifdef _WIN32
closesocket(conn.socket);
#else
close(conn.socket);
#endif
}
}
queue = std::move(new_queue);
}
}
size_t ConnectionPool::get_pool_size() const {
std::lock_guard<std::mutex> lock(pool_mutex_);
size_t total = 0;
for (const auto& host_pair : pool_) {
total += host_pair.second.size();
}
return total;
}
std::string ConnectionPool::make_key(const std::string& host, int port, bool is_ssl) const {
return host + ":" + std::to_string(port) + (is_ssl ? ":ssl" : "");
}
bool ConnectionPool::is_socket_alive(socket_t socket) const {
char test_byte;
int result = ::recv(socket, &test_byte, 1, MSG_PEEK);
#ifdef _WIN32
if (result == SOCKET_ERROR) {
int error = WSAGetLastError();
return (error == WSAEWOULDBLOCK);
}
#else
if (result < 0) {
return (errno == EWOULDBLOCK || errno == EAGAIN);
}
#endif
return true;
}

48
Sources/connection_pool.h Normal file
View File

@ -0,0 +1,48 @@
#pragma once
#include <string>
#include <unordered_map>
#include <queue>
#include <mutex>
#include <chrono>
#ifdef _WIN32
#include <winsock2.h>
typedef SOCKET socket_t;
#else
#include <sys/socket.h>
typedef int socket_t;
#endif
class ConnectionPool {
private:
struct PooledConnection {
socket_t socket;
std::chrono::steady_clock::time_point last_used;
bool is_ssl;
};
std::unordered_map<std::string, std::queue<PooledConnection>> pool_;
mutable std::mutex pool_mutex_;
static constexpr int MAX_CONNECTIONS_PER_HOST = 4;
static constexpr int CONNECTION_TIMEOUT_SECONDS = 30;
public:
ConnectionPool() = default;
~ConnectionPool();
socket_t acquire_connection(const std::string& host, int port, bool is_ssl = false);
// returns connection to pool for reuse
void release_connection(const std::string& host, int port, socket_t socket, bool is_ssl = false);
void cleanup_expired();
size_t get_pool_size() const;
private:
std::string make_key(const std::string& host, int port, bool is_ssl) const;
bool is_socket_alive(socket_t socket) const;
};
extern ConnectionPool g_connection_pool;

View File

@ -0,0 +1,4 @@
#include "global_thread_pool.h"
std::unique_ptr<ThreadPool> GlobalThreadPool::instance_;
std::once_flag GlobalThreadPool::initialized_;

View File

@ -0,0 +1,25 @@
#pragma once
#include "thread_pool.h"
#include <memory>
#include <mutex>
class GlobalThreadPool {
private:
static std::unique_ptr<ThreadPool> instance_;
static std::once_flag initialized_;
public:
static ThreadPool& getInstance() {
std::call_once(initialized_, []() {
unsigned int hw_threads = std::thread::hardware_concurrency();
size_t num_threads = hw_threads > 4 ? hw_threads / 2 : 2;
instance_ = std::make_unique<ThreadPool>(num_threads);
});
return *instance_;
}
static void shutdown() {
instance_.reset();
}
};

2387
Sources/httprequest.cpp Normal file

File diff suppressed because it is too large Load Diff

235
Sources/httprequest.h Normal file
View File

@ -0,0 +1,235 @@
#pragma once
#ifdef WITH_NETWORKING
#include <v8.h>
#include <string>
#include <memory>
#include <map>
#include <functional>
#include <mutex>
#include <queue>
#include <atomic>
#include <thread>
#include <vector>
#ifdef WITH_SSL
#ifndef _WIN32
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/crypto.h>
#endif
#endif
namespace HttpRequestWrapper {
enum ReadyState {
UNSENT = 0,
OPENED = 1,
HEADERS_RECEIVED = 2,
LOADING = 3,
DONE = 4
};
enum ResponseType {
DEFAULT = 0,
ARRAY_BUFFER = 1,
BLOB = 2,
DOCUMENT = 3,
JSON = 4,
TEXT = 5
};
struct HttpResponse {
int status;
std::string statusText;
std::map<std::string, std::string> headers;
std::string responseText;
std::string error;
HttpResponse() : status(0), statusText("") {}
};
class HttpRequestClient {
public:
HttpRequestClient(v8::Isolate* isolate, v8::Global<v8::Context>* global_context);
~HttpRequestClient();
void open(const std::string& method, const std::string& url, bool async = true);
void setRequestHeader(const std::string& header, const std::string& value);
void send(const std::string& data = "");
void abort();
std::string getAllResponseHeaders();
std::string getResponseHeader(const std::string& header);
ReadyState getReadyState() const { return ready_state_; }
int getStatus() const { return response_.status; }
std::string getStatusText() const { return response_.statusText; }
std::string getResponseText() const { return response_.responseText; }
std::string getResponseURL() const { return url_; }
ResponseType getResponseType() const { return response_type_; }
int getTimeout() const { return timeout_; }
bool getWithCredentials() const { return with_credentials_; }
void setTimeout(int timeout) { timeout_ = timeout; }
void setResponseType(ResponseType type) { response_type_ = type; }
void setWithCredentials(bool withCredentials) { with_credentials_ = withCredentials; }
void setOnReadyStateChange(v8::Local<v8::Function> callback);
void setOnLoad(v8::Local<v8::Function> callback);
void setOnError(v8::Local<v8::Function> callback);
void setOnTimeout(v8::Local<v8::Function> callback);
void setOnAbort(v8::Local<v8::Function> callback);
void setOnProgress(v8::Local<v8::Function> callback);
void setOnLoadStart(v8::Local<v8::Function> callback);
void setOnLoadEnd(v8::Local<v8::Function> callback);
// from main thread
void processEvents();
private:
v8::Isolate* isolate_;
v8::Global<v8::Context>* global_context_;
std::string method_;
std::string url_;
std::map<std::string, std::string> request_headers_;
std::string request_data_;
HttpResponse response_;
ReadyState ready_state_;
ResponseType response_type_;
int timeout_;
bool with_credentials_;
bool async_;
std::thread request_thread_;
std::atomic<bool> should_abort_;
v8::Global<v8::Function> on_ready_state_change_;
v8::Global<v8::Function> on_load_;
v8::Global<v8::Function> on_error_;
v8::Global<v8::Function> on_timeout_;
v8::Global<v8::Function> on_abort_;
v8::Global<v8::Function> on_progress_;
v8::Global<v8::Function> on_load_start_;
v8::Global<v8::Function> on_load_end_;
std::mutex state_mutex_;
public:
std::atomic<bool> active_operation_{false};
private:
// TODO: find real solution for duplicate done
bool done_event_fired_{false};
#ifdef WITH_SSL
#ifdef _WIN32
void* ssl_cred_handle_;
void* ssl_context_handle_;
bool ssl_context_initialized_;
int ssl_socket_;
std::vector<char> ssl_buffer_;
#else
SSL_CTX* ssl_ctx_;
SSL* ssl_;
bool ssl_initialized_;
#endif
bool initSSL();
void cleanupSSL();
bool performSSLHandshake(int socket, const std::string& host);
int sslRead(char* buffer, int length);
int sslWrite(const char* buffer, int length);
#endif
enum EventType {
HTTP_READY_STATE_CHANGE = 100,
HTTP_LOAD = 101,
HTTP_ERROR = 102,
HTTP_TIMEOUT = 103,
HTTP_ABORT = 104,
HTTP_PROGRESS = 105,
HTTP_LOAD_START = 106,
HTTP_LOAD_END = 107
};
struct Event {
EventType type;
std::string data;
int code;
Event(EventType t, const std::string& d = "", int c = 0)
: type(t), data(d), code(c) {}
};
std::queue<Event> event_queue_;
std::mutex event_queue_mutex_;
// handle* called from background thread
void handleReadyStateChange();
void handleLoad();
void handleError(const std::string& error);
void handleTimeout();
void handleAbort();
void handleProgress();
void handleLoadStart();
void handleLoadEnd();
// process* called from main thread
void processReadyStateChangeEvent();
void processLoadEvent();
void processErrorEvent(const std::string& error);
void processTimeoutEvent();
void processAbortEvent();
void processProgressEvent();
void processLoadStartEvent();
void processLoadEndEvent();
void performRequest();
void loadLocalFile(const std::string& path);
void setReadyState(ReadyState state);
void callReadyStateChangeCallback();
void callCallback(v8::Global<v8::Function>& callback, int argc, v8::Local<v8::Value> argv[]);
bool parseUrl(const std::string& url, std::string& host, int& port, std::string& path, bool& is_https);
std::string formatHttpRequest(const std::string& method, const std::string& path, const std::string& host, const std::string& data);
void parseHttpResponse(const std::string& response);
};
void initialize();
void cleanup();
// process events from main thread
void processEvents();
int createHttpRequest(v8::Isolate* isolate);
v8::Global<v8::Context>* getGlobalContext();
}
// V8 binding functions called from main.cpp
void runt_httprequest_create(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_open(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_send(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_abort(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_set_request_header(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_get_all_response_headers(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_get_response_header(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_get_ready_state(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_get_status(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_get_status_text(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_get_response_text(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_get_response_url(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_set_timeout(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_set_response_type(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_set_with_credentials(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_set_onreadystatechange(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_set_onload(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_set_onerror(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_set_ontimeout(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_set_onabort(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_set_onprogress(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_set_onloadstart(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_httprequest_set_onloadend(const v8::FunctionCallbackInfo<v8::Value>& args);
void createXMLHttpRequestClass(v8::Isolate* isolate, v8::Local<v8::ObjectTemplate>& global);
#endif

75
Sources/lockfree_queue.h Normal file
View File

@ -0,0 +1,75 @@
#pragma once
#include <atomic>
#include <memory>
template<typename T>
class LockFreeQueue {
private:
struct Node {
std::atomic<T*> data{nullptr};
std::atomic<Node*> next{nullptr};
};
std::atomic<Node*> head_{nullptr};
std::atomic<Node*> tail_{nullptr};
public:
LockFreeQueue() {
Node* dummy = new Node;
head_.store(dummy);
tail_.store(dummy);
}
~LockFreeQueue() {
Node* current = head_.load();
while (current != nullptr) {
Node* next = current->next.load();
T* data = current->data.load();
if (data != nullptr) {
delete data;
}
delete current;
current = next;
}
head_.store(nullptr);
tail_.store(nullptr);
}
void push(T item) {
Node* new_node = new Node;
T* data = new T(std::move(item));
new_node->data.store(data);
Node* prev_tail = tail_.exchange(new_node);
prev_tail->next.store(new_node);
}
bool try_pop(T& result) {
Node* head = head_.load();
Node* next = head->next.load();
if (next == nullptr) {
return false;
}
T* data = next->data.load();
if (data == nullptr) {
return false;
}
result = *data;
delete data;
head_.store(next);
delete head;
return true;
}
bool empty() const {
Node* head = head_.load();
Node* next = head->next.load();
return (next == nullptr);
}
};

File diff suppressed because it is too large Load Diff

259
Sources/ring_buffer.h Normal file
View File

@ -0,0 +1,259 @@
#ifndef RING_BUFFER_H
#define RING_BUFFER_H
#include <cstdint>
#include <cstring>
#include <atomic>
// we use a ring buffer with unmasked indices with power-of-2 size
class RingBuffer {
public:
explicit RingBuffer(size_t capacity = 65536) {
size_t pow2 = 1;
while (pow2 < capacity) pow2 <<= 1;
capacity_ = pow2;
mask_ = pow2 - 1;
buffer_ = new uint8_t[capacity_];
read_idx_ = 0;
write_idx_ = 0;
}
~RingBuffer() {
delete[] buffer_;
}
// no copy and reset buffer to empty state
RingBuffer(const RingBuffer&) = delete;
RingBuffer& operator=(const RingBuffer&) = delete;
void reset() {
read_idx_ = 0;
write_idx_ = 0;
}
size_t writeAvailable() const {
return capacity_ - size();
}
size_t size() const {
return write_idx_ - read_idx_;
}
bool empty() const {
return read_idx_ == write_idx_;
}
bool full() const {
return size() == capacity_;
}
size_t capacity() const {
return capacity_;
}
size_t write(const uint8_t* data, size_t len) {
size_t available = writeAvailable();
if (len > available) len = available;
if (len == 0) return 0;
size_t write_pos = write_idx_ & mask_;
size_t first_chunk = capacity_ - write_pos;
if (first_chunk >= len) {
// single contiguous write
memcpy(buffer_ + write_pos, data, len);
} else {
// wrap around - two writes
memcpy(buffer_ + write_pos, data, first_chunk);
memcpy(buffer_, data + first_chunk, len - first_chunk);
}
write_idx_ += len;
return len;
}
// returns bytes available at offset
size_t peek(uint8_t* dest, size_t len, size_t offset = 0) const {
size_t available = size();
if (offset >= available) return 0;
available -= offset;
if (len > available) len = available;
if (len == 0) return 0;
size_t read_pos = (read_idx_ + offset) & mask_;
size_t first_chunk = capacity_ - read_pos;
if (first_chunk >= len) {
memcpy(dest, buffer_ + read_pos, len);
} else {
memcpy(dest, buffer_ + read_pos, first_chunk);
memcpy(dest + first_chunk, buffer_, len - first_chunk);
}
return len;
}
// read the single byte at offset for parsing the header frame
uint8_t peekByte(size_t offset) const {
if (offset >= size()) return 0;
return buffer_[(read_idx_ + offset) & mask_];
}
void consume(size_t len) {
size_t available = size();
if (len > available) len = available;
read_idx_ += len;
}
size_t read(uint8_t* dest, size_t len) {
size_t bytes_read = peek(dest, len);
consume(bytes_read);
return bytes_read;
}
const uint8_t* getReadPtr(size_t* contiguous_len) const {
size_t read_pos = read_idx_ & mask_;
size_t available = size();
size_t to_end = capacity_ - read_pos;
*contiguous_len = (available < to_end) ? available : to_end;
return buffer_ + read_pos;
}
uint8_t* getWritePtr(size_t* contiguous_len) {
size_t write_pos = write_idx_ & mask_;
size_t available = writeAvailable();
size_t to_end = capacity_ - write_pos;
*contiguous_len = (available < to_end) ? available : to_end;
return buffer_ + write_pos;
}
void commitWrite(size_t len) {
write_idx_ += len;
}
private:
uint8_t* buffer_;
size_t capacity_;
size_t mask_;
size_t read_idx_;
size_t write_idx_;
};
enum class WsFrameResult {
WS_COMPLETE,
WS_INCOMPLETE, // needs more data
WS_ERROR
};
struct WsFrameInfo {
bool fin;
uint8_t opcode;
bool masked;
uint64_t payload_length;
uint32_t mask_key;
size_t header_size; // total size 2-14 bytes
size_t total_size; // header_size + payload_length
};
// frame header from ring buffer returns result and fills frame_info on success
inline WsFrameResult parseWsFrameHeader(const RingBuffer& buf, WsFrameInfo& info) {
size_t available = buf.size();
if (available < 2) return WsFrameResult::WS_INCOMPLETE;
uint8_t byte1 = buf.peekByte(0);
uint8_t byte2 = buf.peekByte(1);
info.fin = (byte1 & 0x80) != 0;
uint8_t rsv = (byte1 >> 4) & 0x07;
info.opcode = byte1 & 0x0F;
info.masked = (byte2 & 0x80) != 0;
info.payload_length = byte2 & 0x7F;
// RSV bits must be 0 unless extension negotiated
if (rsv != 0) {
return WsFrameResult::WS_ERROR;
}
size_t header_size = 2;
if (info.payload_length == 126) {
if (available < 4) return WsFrameResult::WS_INCOMPLETE;
info.payload_length = ((uint64_t)buf.peekByte(2) << 8) | buf.peekByte(3);
header_size = 4;
} else if (info.payload_length == 127) {
if (available < 10) return WsFrameResult::WS_INCOMPLETE;
info.payload_length = 0;
for (int i = 0; i < 8; i++) {
info.payload_length = (info.payload_length << 8) | buf.peekByte(2 + i);
}
header_size = 10;
// max 16MB payload
if (info.payload_length > 16 * 1024 * 1024) {
return WsFrameResult::WS_ERROR;
}
}
// mask key only for client-to-server
if (info.masked) {
if (available < header_size + 4) return WsFrameResult::WS_INCOMPLETE;
info.mask_key = ((uint32_t)buf.peekByte(header_size) << 24) |
((uint32_t)buf.peekByte(header_size + 1) << 16) |
((uint32_t)buf.peekByte(header_size + 2) << 8) |
buf.peekByte(header_size + 3);
header_size += 4;
} else {
info.mask_key = 0;
}
info.header_size = header_size;
info.total_size = header_size + info.payload_length;
if (available < info.total_size) {
return WsFrameResult::WS_INCOMPLETE;
}
return WsFrameResult::WS_COMPLETE;
}
// extract and unmask payload from ring buffer following uWebSockets patterns: fast path for unmasked, 4-byte XOR for masked
inline void extractWsPayload(RingBuffer& buf, const WsFrameInfo& info, uint8_t* dest) {
size_t payload_start = info.header_size;
size_t len = (size_t)info.payload_length;
// skip header bytes first
buf.consume(payload_start);
// unmasked data for server-to-client messages which are never masked
if (!info.masked) {
buf.read(dest, len);
return;
}
// otherwise read then unmask
buf.read(dest, len);
// extracted mask bytes in network byte order
uint8_t mask[4] = {
(uint8_t)((info.mask_key >> 24) & 0xFF),
(uint8_t)((info.mask_key >> 16) & 0xFF),
(uint8_t)((info.mask_key >> 8) & 0xFF),
(uint8_t)(info.mask_key & 0xFF)
};
// use 4-byte operations like uWebSockets
size_t i = 0;
size_t chunks = len >> 2; // len / 4
for (size_t c = 0; c < chunks; c++, i += 4) {
dest[i] ^= mask[0];
dest[i + 1] ^= mask[1];
dest[i + 2] ^= mask[2];
dest[i + 3] ^= mask[3];
}
for (; i < len; i++) {
dest[i] ^= mask[i % 4];
}
}
#endif

947
Sources/socket_bridge.cpp Normal file
View File

@ -0,0 +1,947 @@
#include "socket_bridge.h"
#include <kinc/log.h>
#include <map>
#include <memory>
#include <string>
#include <vector>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "wsock32.lib")
#ifdef WITH_SSL
#ifdef _WIN32
#define SECURITY_WIN32
#include <wincrypt.h>
#include <schannel.h>
#include <security.h>
#include <sspi.h>
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "secur32.lib")
#endif
#endif
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#ifdef WITH_SSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#endif
#endif
class RunTSocket {
public:
int id;
bool isBlocking;
bool isConnected;
bool isBound;
bool isListening;
std::string lastError;
#ifdef _WIN32
SOCKET socket_fd;
#else
int socket_fd;
#endif
#ifdef WITH_SSL
#ifdef _WIN32
// windows SChannel SSL support with opaque pointers
void* ssl_cred_handle_;
void* ssl_context_handle_;
bool ssl_context_initialized_;
std::vector<char> ssl_buffer_;
#else
SSL* ssl;
SSL_CTX* ssl_ctx;
#endif
bool useSSL;
#endif
RunTSocket(int socket_id) : id(socket_id), isBlocking(true), isConnected(false),
isBound(false), isListening(false) {
#ifdef _WIN32
socket_fd = INVALID_SOCKET;
#else
socket_fd = -1;
#endif
#ifdef WITH_SSL
#ifdef _WIN32
ssl_cred_handle_ = nullptr;
ssl_context_handle_ = nullptr;
ssl_context_initialized_ = false;
#else
ssl = nullptr;
ssl_ctx = nullptr;
#endif
useSSL = false;
#endif
}
~RunTSocket() {
close();
}
void close() {
if (isValid()) {
#ifdef WITH_SSL
#ifdef _WIN32
if (ssl_context_handle_) {
ssl_context_handle_ = nullptr;
}
if (ssl_cred_handle_) {
ssl_cred_handle_ = nullptr;
}
ssl_context_initialized_ = false;
#else
if (ssl) {
SSL_shutdown(ssl);
SSL_free(ssl);
ssl = nullptr;
}
if (ssl_ctx) {
SSL_CTX_free(ssl_ctx);
ssl_ctx = nullptr;
}
#endif
#endif
#ifdef _WIN32
shutdown(socket_fd, SD_SEND);
closesocket(socket_fd);
socket_fd = INVALID_SOCKET;
#else
shutdown(socket_fd, SHUT_WR);
::close(socket_fd);
socket_fd = -1;
#endif
}
isConnected = false;
isBound = false;
isListening = false;
}
bool isValid() const {
#ifdef _WIN32
return socket_fd != INVALID_SOCKET;
#else
return socket_fd >= 0;
#endif
}
};
// global socket
static std::map<int, std::unique_ptr<RunTSocket>> g_sockets;
static int g_next_socket_id = 1;
static bool g_winsock_initialized = false;
static bool initialize_networking() {
if (g_winsock_initialized) return true;
#ifdef _WIN32
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
kinc_log(KINC_LOG_LEVEL_ERROR, "WSAStartup failed: %d", result);
return false;
}
#endif
#ifdef WITH_SSL
#ifdef _WIN32
// no global initialization needed for windows
kinc_log(KINC_LOG_LEVEL_INFO, "Windows SChannel SSL support initialized");
#else
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
kinc_log(KINC_LOG_LEVEL_INFO, "OpenSSL support initialized");
#endif
#endif
g_winsock_initialized = true;
kinc_log(KINC_LOG_LEVEL_INFO, "Networking initialized successfully");
return true;
}
static void cleanup_networking() {
if (!g_winsock_initialized) return;
g_sockets.clear();
#ifdef WITH_SSL
#ifdef _WIN32
kinc_log(KINC_LOG_LEVEL_INFO, "Windows SChannel SSL support cleaned up");
#else
EVP_cleanup();
kinc_log(KINC_LOG_LEVEL_INFO, "OpenSSL support cleaned up");
#endif
#endif
#ifdef _WIN32
WSACleanup();
#endif
g_winsock_initialized = false;
kinc_log(KINC_LOG_LEVEL_INFO, "Networking cleaned up");
}
// C++ Bridge Function Implementations
extern "C" int runt_socket_create() {
if (!initialize_networking()) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to initialize networking");
return -1;
}
int socket_id = g_next_socket_id++;
auto socket = std::make_unique<RunTSocket>(socket_id);
#ifdef _WIN32
socket->socket_fd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socket->socket_fd == INVALID_SOCKET) {
int error = WSAGetLastError();
kinc_log(KINC_LOG_LEVEL_ERROR, "Socket creation failed: %d", error);
return -1;
}
#else
socket->socket_fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (socket->socket_fd < 0) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Socket creation failed: %s", strerror(errno));
return -1;
}
#endif
int nodelay = 1;
setsockopt(socket->socket_fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&nodelay, sizeof(nodelay));
int reuse = 1;
setsockopt(socket->socket_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse));
#if defined(__linux__) && defined(SO_REUSEPORT)
int reuseport = 1;
setsockopt(socket->socket_fd, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuseport, sizeof(reuseport));
#endif
int keepalive = 1;
setsockopt(socket->socket_fd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&keepalive, sizeof(keepalive));
// uWebsockets uses 1mb socket buffer sizes for high throughput
int sndbuf = 1024 * 1024;
int rcvbuf = 1024 * 1024;
setsockopt(socket->socket_fd, SOL_SOCKET, SO_SNDBUF, (const char*)&sndbuf, sizeof(sndbuf));
setsockopt(socket->socket_fd, SOL_SOCKET, SO_RCVBUF, (const char*)&rcvbuf, sizeof(rcvbuf));
#ifdef __APPLE__
// prevent SIGPIPE crashes on macOS
int no_sigpipe = 1;
setsockopt(socket->socket_fd, SOL_SOCKET, SO_NOSIGPIPE, (const char*)&no_sigpipe, sizeof(no_sigpipe));
#endif
g_sockets[socket_id] = std::move(socket);
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Socket created successfully: ID %d", socket_id);
#endif
return socket_id;
}
extern "C" bool runt_socket_bind(int socket_id, const char* address, int port) {
auto it = g_sockets.find(socket_id);
if (it == g_sockets.end()) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Invalid socket ID: %d", socket_id);
return false;
}
auto& socket = it->second;
if (!socket->isValid()) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Socket not valid for bind: %d", socket_id);
return false;
}
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (address && strlen(address) > 0) {
if (inet_pton(AF_INET, address, &addr.sin_addr) <= 0) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Invalid address: %s", address);
return false;
}
} else {
addr.sin_addr.s_addr = INADDR_ANY;
}
if (bind(socket->socket_fd, (sockaddr*)&addr, sizeof(addr)) < 0) {
#ifdef _WIN32
int error = WSAGetLastError();
kinc_log(KINC_LOG_LEVEL_ERROR, "Bind failed: %d", error);
#else
kinc_log(KINC_LOG_LEVEL_ERROR, "Bind failed: %s", strerror(errno));
#endif
return false;
}
socket->isBound = true;
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Socket bound successfully: %s:%d", address ? address : "0.0.0.0", port);
#endif
return true;
}
extern "C" bool runt_socket_listen(int socket_id, int backlog) {
auto it = g_sockets.find(socket_id);
if (it == g_sockets.end()) return false;
auto& socket = it->second;
if (!socket->isValid() || !socket->isBound) return false;
if (listen(socket->socket_fd, backlog) < 0) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Listen failed");
return false;
}
socket->isListening = true;
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Socket listening: ID %d", socket_id);
#endif
return true;
}
extern "C" int runt_socket_accept(int socket_id) {
auto it = g_sockets.find(socket_id);
if (it == g_sockets.end()) return -1;
auto& socket = it->second;
if (!socket->isValid() || !socket->isListening) return -1;
sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
#ifdef _WIN32
SOCKET client_fd = accept(socket->socket_fd, (sockaddr*)&client_addr, &client_len);
if (client_fd == INVALID_SOCKET) return -1;
#else
int client_fd = accept(socket->socket_fd, (sockaddr*)&client_addr, &client_len);
if (client_fd < 0) return -1;
#endif
int client_id = g_next_socket_id++;
auto client_socket = std::make_unique<RunTSocket>(client_id);
client_socket->socket_fd = client_fd;
client_socket->isConnected = true;
int nodelay = 1;
setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&nodelay, sizeof(nodelay));
int keepalive = 1;
setsockopt(client_fd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&keepalive, sizeof(keepalive));
int sndbuf = 1024 * 1024;
int rcvbuf = 1024 * 1024;
setsockopt(client_fd, SOL_SOCKET, SO_SNDBUF, (const char*)&sndbuf, sizeof(sndbuf));
setsockopt(client_fd, SOL_SOCKET, SO_RCVBUF, (const char*)&rcvbuf, sizeof(rcvbuf));
#ifdef __APPLE__
int no_sigpipe = 1;
setsockopt(client_fd, SOL_SOCKET, SO_NOSIGPIPE, (const char*)&no_sigpipe, sizeof(no_sigpipe));
#endif
g_sockets[client_id] = std::move(client_socket);
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Socket accepted: ID %d", client_id);
#endif
return client_id;
}
extern "C" bool runt_socket_connect(int socket_id, const char* hostname, int port) {
auto it = g_sockets.find(socket_id);
if (it == g_sockets.end()) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Invalid socket ID: %d", socket_id);
return false;
}
auto& socket = it->second;
if (!socket->isValid()) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Socket not valid for connect: %d", socket_id);
return false;
}
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Connecting to %s:%d", hostname, port);
#endif
struct addrinfo hints, *result;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
char port_str[16];
snprintf(port_str, sizeof(port_str), "%d", port);
int status = getaddrinfo(hostname, port_str, &hints, &result);
if (status != 0) {
kinc_log(KINC_LOG_LEVEL_ERROR, "getaddrinfo failed for %s: %s", hostname, gai_strerror(status));
return false;
}
bool connected = false;
for (struct addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) {
if (connect(socket->socket_fd, rp->ai_addr, static_cast<int>(rp->ai_addrlen)) == 0) {
connected = true;
break;
}
}
freeaddrinfo(result);
if (!connected) {
#ifdef _WIN32
int error = WSAGetLastError();
kinc_log(KINC_LOG_LEVEL_ERROR, "Connect failed to %s:%d - Error: %d", hostname, port, error);
#else
kinc_log(KINC_LOG_LEVEL_ERROR, "Connect failed to %s:%d - Error: %s", hostname, port, strerror(errno));
#endif
return false;
}
socket->isConnected = true;
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Connected successfully to %s:%d", hostname, port);
#endif
return true;
}
extern "C" int runt_socket_send(int socket_id, const char* data, int length) {
auto it = g_sockets.find(socket_id);
if (it == g_sockets.end()) return -1;
auto& socket = it->second;
if (!socket->isValid() || !socket->isConnected) return -1;
#ifdef WITH_SSL
if (socket->useSSL) {
#ifdef _WIN32
// send implementation adapted from httprequest.cpp
if (!socket->ssl_context_initialized_) {
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL context not initialized");
return -1;
}
SecPkgContext_StreamSizes stream_sizes;
SECURITY_STATUS status = QueryContextAttributesA(
reinterpret_cast<PCtxtHandle>(socket->ssl_context_handle_),
SECPKG_ATTR_STREAM_SIZES, &stream_sizes);
if (status != SEC_E_OK) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to get stream sizes: 0x%x", status);
return -1;
}
int total_size = stream_sizes.cbHeader + length + stream_sizes.cbTrailer;
char* encrypt_buffer = new char[total_size];
SecBufferDesc message_desc;
SecBuffer message_buffers[4];
message_desc.ulVersion = SECBUFFER_VERSION;
message_desc.cBuffers = 4;
message_desc.pBuffers = message_buffers;
message_buffers[0].BufferType = SECBUFFER_STREAM_HEADER;
message_buffers[0].pvBuffer = encrypt_buffer;
message_buffers[0].cbBuffer = stream_sizes.cbHeader;
message_buffers[1].BufferType = SECBUFFER_DATA;
message_buffers[1].pvBuffer = encrypt_buffer + stream_sizes.cbHeader;
message_buffers[1].cbBuffer = length;
memcpy(message_buffers[1].pvBuffer, data, length);
message_buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
message_buffers[2].pvBuffer = encrypt_buffer + stream_sizes.cbHeader + length;
message_buffers[2].cbBuffer = stream_sizes.cbTrailer;
message_buffers[3].BufferType = SECBUFFER_EMPTY;
message_buffers[3].pvBuffer = nullptr;
message_buffers[3].cbBuffer = 0;
status = EncryptMessage(
reinterpret_cast<PCtxtHandle>(socket->ssl_context_handle_),
0, &message_desc, 0);
if (status != SEC_E_OK) {
kinc_log(KINC_LOG_LEVEL_ERROR, "SChannel encrypt failed: 0x%x", status);
delete[] encrypt_buffer;
return -1;
}
int total_encrypted = message_buffers[0].cbBuffer + message_buffers[1].cbBuffer + message_buffers[2].cbBuffer;
int sent = send(socket->socket_fd, encrypt_buffer, total_encrypted, 0);
delete[] encrypt_buffer;
if (sent != total_encrypted) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to send encrypted SSL data");
return -1;
}
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "SSL sent %d bytes (encrypted: %d bytes)", length, total_encrypted);
#endif
return length;
#else
if (socket->ssl) {
int sent = SSL_write(socket->ssl, data, length);
if (sent <= 0) {
int ssl_error = SSL_get_error(socket->ssl, sent);
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL_write failed: %d", ssl_error);
return -1;
}
return sent;
}
#endif
}
#endif
#ifdef _WIN32
int sent = send(socket->socket_fd, data, length, 0);
#else
int sent = send(socket->socket_fd, data, length, MSG_NOSIGNAL);
#endif
if (sent < 0) {
#ifdef _WIN32
int error = WSAGetLastError();
if (error == WSAEWOULDBLOCK) {
return 0;
}
// connection reset/aborted we mark as disconnected
if (error == WSAECONNRESET || error == WSAECONNABORTED || error == WSAENETRESET) {
socket->isConnected = false;
}
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Send failed: %d", error);
#endif
#else
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return 0;
}
if (errno == ECONNRESET || errno == ENOTCONN || errno == EPIPE) {
socket->isConnected = false;
}
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Send failed: %s", strerror(errno));
#endif
#endif
return -1;
}
#ifdef DEBUG_NETWORK
if (sent > 10) {
kinc_log(KINC_LOG_LEVEL_INFO, "Sent %d bytes", sent);
}
#endif
return sent;
}
extern "C" int runt_socket_recv(int socket_id, int max_length, char** out_data) {
auto it = g_sockets.find(socket_id);
if (it == g_sockets.end()) return -1;
auto& socket = it->second;
if (!socket->isValid() || !socket->isConnected) return -1;
char* buffer = new char[max_length];
#ifdef WITH_SSL
if (socket->useSSL) {
#ifdef _WIN32
if (!socket->ssl_context_initialized_) {
delete[] buffer;
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL context not initialized");
return -1;
}
while (true) {
// decrypt for buffered data
if (!socket->ssl_buffer_.empty()) {
SecBufferDesc message_desc;
SecBuffer message_buffers[4];
// buffer descriptor for decryption
message_desc.ulVersion = SECBUFFER_VERSION;
message_desc.cBuffers = 4;
message_desc.pBuffers = message_buffers;
// input buffer with encrypted data
message_buffers[0].BufferType = SECBUFFER_DATA;
message_buffers[0].pvBuffer = socket->ssl_buffer_.data();
message_buffers[0].cbBuffer = static_cast<unsigned long>(socket->ssl_buffer_.size());
// output buffer
message_buffers[1].BufferType = SECBUFFER_EMPTY;
message_buffers[1].pvBuffer = nullptr;
message_buffers[1].cbBuffer = 0;
// extra buffer for leftover data
message_buffers[2].BufferType = SECBUFFER_EMPTY;
message_buffers[2].pvBuffer = nullptr;
message_buffers[2].cbBuffer = 0;
// stream buffer
message_buffers[3].BufferType = SECBUFFER_EMPTY;
message_buffers[3].pvBuffer = nullptr;
message_buffers[3].cbBuffer = 0;
SECURITY_STATUS status = DecryptMessage(
reinterpret_cast<PCtxtHandle>(socket->ssl_context_handle_),
&message_desc, 0, nullptr);
if (status == SEC_E_OK) {
SecBuffer* data_buffer = nullptr;
SecBuffer* extra_buffer = nullptr;
// find data and extra buffers
for (int i = 0; i < 4; i++) {
if (message_buffers[i].BufferType == SECBUFFER_DATA) {
data_buffer = &message_buffers[i];
} else if (message_buffers[i].BufferType == SECBUFFER_EXTRA) {
extra_buffer = &message_buffers[i];
}
}
if (data_buffer && data_buffer->cbBuffer > 0) {
int bytes_to_copy = (max_length < static_cast<int>(data_buffer->cbBuffer)) ? max_length : static_cast<int>(data_buffer->cbBuffer);
memcpy(buffer, data_buffer->pvBuffer, bytes_to_copy);
// move extra data to the beginning of the buffer
if (extra_buffer && extra_buffer->cbBuffer > 0) {
std::memmove(socket->ssl_buffer_.data(), extra_buffer->pvBuffer, extra_buffer->cbBuffer);
socket->ssl_buffer_.resize(extra_buffer->cbBuffer);
} else {
socket->ssl_buffer_.clear();
}
*out_data = buffer;
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "SSL received %d bytes (decrypted)", bytes_to_copy);
#endif
return bytes_to_copy;
}
socket->ssl_buffer_.clear();
} else if (status == SEC_E_INCOMPLETE_MESSAGE) {
// needs more from the socket
char recv_buffer[8192];
int received = recv(socket->socket_fd, recv_buffer, sizeof(recv_buffer), 0);
if (received <= 0) {
delete[] buffer;
if (received == 0) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "SSL connection closed by server");
#endif
return 0;
} else {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive encrypted data for SSL read");
return -1;
}
}
socket->ssl_buffer_.insert(socket->ssl_buffer_.end(), recv_buffer, recv_buffer + received);
continue;
} else {
delete[] buffer;
kinc_log(KINC_LOG_LEVEL_ERROR, "SChannel decrypt failed: 0x%x", status);
return -1;
}
}
char recv_buffer[8192];
int received = recv(socket->socket_fd, recv_buffer, sizeof(recv_buffer), 0);
if (received <= 0) {
delete[] buffer;
if (received == 0) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "SSL connection closed by server (no buffered data)");
#endif
return 0;
} else {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive encrypted data");
return -1;
}
}
socket->ssl_buffer_.insert(socket->ssl_buffer_.end(), recv_buffer, recv_buffer + received);
}
#else
if (socket->ssl) {
int received = SSL_read(socket->ssl, buffer, max_length);
if (received <= 0) {
delete[] buffer;
int ssl_error = SSL_get_error(socket->ssl, received);
if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
return 0;
}
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL_read failed: %d", ssl_error);
return -1;
}
*out_data = buffer;
return received;
}
#endif
}
#endif
int received = recv(socket->socket_fd, buffer, max_length, 0);
if (received < 0) {
#ifdef _WIN32
int error = WSAGetLastError();
if (error == WSAEWOULDBLOCK) {
delete[] buffer;
return 0;
}
// WSAECONNRESET (10054) = connection reset by peer
// WSAECONNABORTED (10053) = connection aborted
// WSAENETRESET (10052) = Network dropped connection
// Treat all == disconnected
if (error == WSAECONNRESET || error == WSAECONNABORTED || error == WSAENETRESET) {
delete[] buffer;
socket->isConnected = false;
return -2;
}
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Recv failed: %d", error);
#endif
#else
if (errno == EAGAIN || errno == EWOULDBLOCK) {
delete[] buffer;
return 0;
}
if (errno == ECONNRESET || errno == ENOTCONN || errno == EPIPE) {
delete[] buffer;
socket->isConnected = false;
return -2;
}
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_ERROR, "Recv failed: %s", strerror(errno));
#endif
#endif
delete[] buffer;
return -1;
}
if (received == 0) {
delete[] buffer;
socket->isConnected = false;
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Connection closed by peer");
#endif
return -2; // Distinct value for connection closed
}
*out_data = buffer;
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Received %d bytes", received);
#endif
return received;
}
extern "C" void runt_socket_close(int socket_id) {
auto it = g_sockets.find(socket_id);
if (it == g_sockets.end()) return;
it->second->close();
g_sockets.erase(it);
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Socket closed: ID %d", socket_id);
#endif
}
extern "C" bool runt_socket_is_connected(int socket_id) {
auto it = g_sockets.find(socket_id);
if (it == g_sockets.end()) return false;
auto& socket = it->second;
return socket->isValid() && socket->isConnected;
}
extern "C" void runt_socket_set_blocking(int socket_id, bool blocking) {
auto it = g_sockets.find(socket_id);
if (it == g_sockets.end()) return;
auto& socket = it->second;
if (!socket->isValid()) return;
#ifdef _WIN32
u_long mode = blocking ? 0 : 1;
ioctlsocket(socket->socket_fd, FIONBIO, &mode);
#else
int flags = fcntl(socket->socket_fd, F_GETFL, 0);
if (blocking) {
flags &= ~O_NONBLOCK;
} else {
flags |= O_NONBLOCK;
}
fcntl(socket->socket_fd, F_SETFL, flags);
#endif
socket->isBlocking = blocking;
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "Socket blocking mode set: %s", blocking ? "blocking" : "non-blocking");
#endif
}
extern "C" bool runt_socket_select(int socket_id, double timeout_sec) {
auto it = g_sockets.find(socket_id);
if (it == g_sockets.end()) return false;
auto& socket = it->second;
if (!socket->isValid()) return false;
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(socket->socket_fd, &readfds);
struct timeval tv;
tv.tv_sec = (long)timeout_sec;
tv.tv_usec = (long)((timeout_sec - tv.tv_sec) * 1000000);
int result = ::select((int)socket->socket_fd + 1, &readfds, nullptr, nullptr, &tv);
if (result > 0 && FD_ISSET(socket->socket_fd, &readfds)) {
return true;
}
return false;
}
#ifdef WITH_SSL
extern "C" bool runt_socket_enable_ssl(int socket_id) {
auto it = g_sockets.find(socket_id);
if (it == g_sockets.end()) return false;
auto& socket = it->second;
if (!socket->isValid() || !socket->isConnected) return false;
#ifdef _WIN32
if (socket->useSSL) return true;
if (!socket->ssl_cred_handle_) {
socket->ssl_cred_handle_ = new CredHandle();
memset(socket->ssl_cred_handle_, 0, sizeof(CredHandle));
SCHANNEL_CRED credentials;
memset(&credentials, 0, sizeof(credentials));
credentials.dwVersion = SCHANNEL_CRED_VERSION;
credentials.grbitEnabledProtocols = SP_PROT_TLS1_2;
credentials.dwFlags = SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_MANUAL_CRED_VALIDATION;
TimeStamp expiry;
SECURITY_STATUS status = AcquireCredentialsHandleA(
nullptr, const_cast<char*>(UNISP_NAME_A), SECPKG_CRED_OUTBOUND,
nullptr, &credentials, nullptr, nullptr,
reinterpret_cast<PCredHandle>(socket->ssl_cred_handle_), &expiry);
if (status != SEC_E_OK) {
kinc_log(KINC_LOG_LEVEL_ERROR, "AcquireCredentialsHandleA failed: 0x%x", status);
delete reinterpret_cast<CredHandle*>(socket->ssl_cred_handle_);
socket->ssl_cred_handle_ = nullptr;
return false;
}
kinc_log(KINC_LOG_LEVEL_INFO, "SSL credentials acquired successfully");
}
SecBufferDesc outbuffer_desc;
SecBuffer outbuffers[1];
DWORD context_attributes;
TimeStamp expiry;
outbuffers[0].pvBuffer = nullptr;
outbuffers[0].BufferType = SECBUFFER_TOKEN;
outbuffers[0].cbBuffer = 0;
outbuffer_desc.cBuffers = 1;
outbuffer_desc.pBuffers = outbuffers;
outbuffer_desc.ulVersion = SECBUFFER_VERSION;
socket->ssl_context_handle_ = new CtxtHandle();
memset(socket->ssl_context_handle_, 0, sizeof(CtxtHandle));
// TODO: Use actual hostname
SECURITY_STATUS status = InitializeSecurityContextA(
reinterpret_cast<PCredHandle>(socket->ssl_cred_handle_),
nullptr,
const_cast<char*>("localhost"),
ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
ISC_REQ_CONFIDENTIALITY | ISC_REQ_EXTENDED_ERROR |
ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM,
0, SECURITY_NATIVE_DREP, nullptr, 0,
reinterpret_cast<PCtxtHandle>(socket->ssl_context_handle_),
&outbuffer_desc, &context_attributes, &expiry);
if (status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK) {
// send handshake data if available
if (outbuffers[0].cbBuffer > 0 && outbuffers[0].pvBuffer) {
int sent = send(socket->socket_fd, reinterpret_cast<char*>(outbuffers[0].pvBuffer), outbuffers[0].cbBuffer, 0);
if (sent != static_cast<int>(outbuffers[0].cbBuffer)) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to send SSL handshake data");
FreeContextBuffer(outbuffers[0].pvBuffer);
return false;
}
FreeContextBuffer(outbuffers[0].pvBuffer);
}
socket->ssl_context_initialized_ = true;
kinc_log(KINC_LOG_LEVEL_INFO, "SSL handshake initiated successfully");
} else {
kinc_log(KINC_LOG_LEVEL_ERROR, "InitializeSecurityContextA failed: 0x%x", status);
delete reinterpret_cast<CtxtHandle*>(socket->ssl_context_handle_);
socket->ssl_context_handle_ = nullptr;
return false;
}
#else
if (socket->ssl) return true;
socket->ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!socket->ssl_ctx) {
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL_CTX_new failed");
return false;
}
socket->ssl = SSL_new(socket->ssl_ctx);
if (!socket->ssl) {
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL_new failed");
SSL_CTX_free(socket->ssl_ctx);
socket->ssl_ctx = nullptr;
return false;
}
SSL_set_fd(socket->ssl, socket->socket_fd);
int ret = SSL_connect(socket->ssl);
if (ret <= 0) {
int ssl_error = SSL_get_error(socket->ssl, ret);
kinc_log(KINC_LOG_LEVEL_ERROR, "SSL_connect failed: %d", ssl_error);
SSL_free(socket->ssl);
SSL_CTX_free(socket->ssl_ctx);
socket->ssl = nullptr;
socket->ssl_ctx = nullptr;
return false;
}
#endif
socket->useSSL = true;
kinc_log(KINC_LOG_LEVEL_INFO, "SSL enabled successfully for socket %d", socket_id);
return true;
}
#endif
extern "C" void runt_socket_cleanup() {
cleanup_networking();
}

25
Sources/socket_bridge.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
int runt_socket_create();
void runt_socket_close(int socket_id);
void runt_socket_set_blocking(int socket_id, bool blocking);
bool runt_socket_select(int socket_id, double timeout_sec);
bool runt_socket_is_connected(int socket_id);
bool runt_socket_bind(int socket_id, const char* address, int port);
bool runt_socket_listen(int socket_id, int backlog);
int runt_socket_accept(int socket_id);
bool runt_socket_connect(int socket_id, const char* hostname, int port);
int runt_socket_send(int socket_id, const char* data, int length);
int runt_socket_recv(int socket_id, int max_length, char** out_data);
#ifdef WITH_SSL
bool runt_socket_enable_ssl(int socket_id);
#endif
void runt_socket_cleanup();
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,86 @@
#pragma once
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#endif
namespace SocketOptimization {
// buffer sizes for different use cases
constexpr int WEBSOCKET_BUFFER_SIZE = 32768; // 32KB for WebSocket frames
constexpr int HTTP_BUFFER_SIZE = 65536; // 64KB for HTTP responses
constexpr int SSL_BUFFER_SIZE = 16384; // 16KB for SSL/TLS data
constexpr int SMALL_BUFFER_SIZE = 8192; // 8KB for small reads
constexpr int SOCKET_SEND_BUFFER = 256 * 1024; // 256KB send buffer
constexpr int SOCKET_RECV_BUFFER = 256 * 1024; // 256KB receive buffer
inline bool optimizeSocket(int socket_fd) {
if (socket_fd < 0) return false;
int nodelay = 1;
if (setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY,
(const char*)&nodelay, sizeof(nodelay)) != 0) {
return false;
}
int send_buffer = SOCKET_SEND_BUFFER;
if (setsockopt(socket_fd, SOL_SOCKET, SO_SNDBUF,
(const char*)&send_buffer, sizeof(send_buffer)) != 0) {
return false;
}
int recv_buffer = SOCKET_RECV_BUFFER;
if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF,
(const char*)&recv_buffer, sizeof(recv_buffer)) != 0) {
return false;
}
// disable Nagle
int tcp_quickack = 1;
#ifndef _WIN32
setsockopt(socket_fd, IPPROTO_TCP, TCP_QUICKACK,
(const char*)&tcp_quickack, sizeof(tcp_quickack));
#endif
return true;
}
inline bool optimizeWebSocket(int socket_fd) {
if (!optimizeSocket(socket_fd)) return false;
int keepalive = 1;
if (setsockopt(socket_fd, SOL_SOCKET, SO_KEEPALIVE,
(const char*)&keepalive, sizeof(keepalive)) != 0) {
return false;
}
#ifndef _WIN32
// unix keep-alive
int keepidle = 30; // start probes after 30 seconds
int keepintvl = 5; // probe every 5 seconds
int keepcnt = 3; // 3 failed probes before timeout
setsockopt(socket_fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle));
setsockopt(socket_fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl));
setsockopt(socket_fd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt));
#endif
return true;
}
inline bool optimizeHttp(int socket_fd) {
if (!optimizeSocket(socket_fd)) return false;
int reuse = 1;
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
(const char*)&reuse, sizeof(reuse)) != 0) {
return false;
}
return true;
}
};

View File

@ -0,0 +1,311 @@
#include "socket_v8_bindings.h"
#include "socket_bridge.h"
#include <v8.h>
#include <kinc/log.h>
using namespace v8;
// V8 wrapper functions for socket bridge
void v8_runt_socket_create(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
int socket_id = runt_socket_create();
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "[V8] runt_socket_create returned: %d", socket_id);
#endif
args.GetReturnValue().Set(Integer::New(isolate, socket_id));
}
void v8_runt_socket_bind(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 3 || !args[0]->IsInt32() || !args[1]->IsString() || !args[2]->IsInt32()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked()));
return;
}
int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
String::Utf8Value address(isolate, args[1]);
int port = args[2]->Int32Value(isolate->GetCurrentContext()).FromJust();
bool result = runt_socket_bind(socket_id, *address, port);
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "[V8] runt_socket_bind(%d, %s, %d) returned: %s", socket_id, *address, port, result ? "true" : "false");
#endif
args.GetReturnValue().Set(Boolean::New(isolate, result));
}
void v8_runt_socket_listen(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 2 || !args[0]->IsInt32() || !args[1]->IsInt32()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked()));
return;
}
int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
int backlog = args[1]->Int32Value(isolate->GetCurrentContext()).FromJust();
bool result = runt_socket_listen(socket_id, backlog);
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "[V8] runt_socket_listen(%d, %d) returned: %s", socket_id, backlog, result ? "true" : "false");
#endif
args.GetReturnValue().Set(Boolean::New(isolate, result));
}
void v8_runt_socket_accept(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsInt32()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked()));
return;
}
int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
int client_id = runt_socket_accept(socket_id);
#ifdef DEBUG_NETWORK
if (client_id >= 0) {
kinc_log(KINC_LOG_LEVEL_INFO, "[V8] runt_socket_accept(%d) returned: %d", socket_id, client_id);
}
#endif
args.GetReturnValue().Set(Integer::New(isolate, client_id));
}
void v8_runt_socket_connect(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 3 || !args[0]->IsInt32() || !args[1]->IsString() || !args[2]->IsInt32()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked()));
return;
}
int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
String::Utf8Value hostname(isolate, args[1]);
int port = args[2]->Int32Value(isolate->GetCurrentContext()).FromJust();
bool result = runt_socket_connect(socket_id, *hostname, port);
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "[V8] runt_socket_connect(%d, %s, %d) returned: %s", socket_id, *hostname, port, result ? "true" : "false");
#endif
args.GetReturnValue().Set(Boolean::New(isolate, result));
}
void v8_runt_socket_send(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 2 || !args[0]->IsInt32()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked()));
return;
}
int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
int sent = 0;
// look back over how we need to handle buffers and strings as string::Utf8Value must remain
if (args[1]->IsString()) {
String::Utf8Value str_data(isolate, args[1]);
if (*str_data == nullptr) {
kinc_log(KINC_LOG_LEVEL_ERROR, "[V8] runt_socket_send: string conversion failed");
args.GetReturnValue().Set(Integer::New(isolate, -1));
return;
}
int length = str_data.length();
// send while str_data fixes dangling pointer bug
sent = runt_socket_send(socket_id, *str_data, length);
} else if (args[1]->IsArrayBuffer()) {
Local<ArrayBuffer> buffer = Local<ArrayBuffer>::Cast(args[1]);
std::shared_ptr<BackingStore> backing = buffer->GetBackingStore();
const char* data = static_cast<const char*>(backing->Data());
int length = static_cast<int>(backing->ByteLength());
sent = runt_socket_send(socket_id, data, length);
} else if (args[1]->IsArrayBufferView()) {
Local<ArrayBufferView> view = Local<ArrayBufferView>::Cast(args[1]);
Local<ArrayBuffer> buffer = view->Buffer();
std::shared_ptr<BackingStore> backing = buffer->GetBackingStore();
size_t offset = view->ByteOffset();
const char* data = static_cast<const char*>(backing->Data()) + offset;
int length = static_cast<int>(view->ByteLength());
sent = runt_socket_send(socket_id, data, length);
} else {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Data must be string, ArrayBuffer, or TypedArray").ToLocalChecked()));
return;
}
args.GetReturnValue().Set(Integer::New(isolate, sent));
}
void v8_runt_socket_recv(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 2 || !args[0]->IsInt32() || !args[1]->IsInt32()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked()));
return;
}
int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
int max_length = args[1]->Int32Value(isolate->GetCurrentContext()).FromJust();
char* data = nullptr;
int received = runt_socket_recv(socket_id, max_length, &data);
if (received > 0 && data) {
// ArrayBuffer with received data
std::unique_ptr<BackingStore> backing = ArrayBuffer::NewBackingStore(
data, received, [](void* data, size_t length, void* deleter_data) {
delete[] static_cast<char*>(data);
}, nullptr);
Local<ArrayBuffer> buffer = ArrayBuffer::New(isolate, std::move(backing));
args.GetReturnValue().Set(buffer);
} else if (received == -2) {
// closed by peer -> return -2 so Haxe can detect
args.GetReturnValue().Set(Integer::New(isolate, -2));
} else {
// return null for would-block (0) or error (-1)
args.GetReturnValue().Set(Null(isolate));
}
}
void v8_runt_socket_close(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsInt32()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked()));
return;
}
int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
runt_socket_close(socket_id);
args.GetReturnValue().Set(Undefined(isolate));
}
void v8_runt_socket_set_blocking(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 2 || !args[0]->IsInt32() || !args[1]->IsBoolean()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked()));
return;
}
int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
bool blocking = args[1]->BooleanValue(isolate);
runt_socket_set_blocking(socket_id, blocking);
args.GetReturnValue().Set(Undefined(isolate));
}
void v8_runt_socket_select(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsInt32()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked()));
return;
}
int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
double timeout = 0.0;
if (args.Length() >= 2 && args[1]->IsNumber()) {
timeout = args[1]->NumberValue(isolate->GetCurrentContext()).FromJust();
}
bool hasData = runt_socket_select(socket_id, timeout);
args.GetReturnValue().Set(Boolean::New(isolate, hasData));
}
void v8_runt_socket_is_connected(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsInt32()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked()));
return;
}
int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
bool connected = runt_socket_is_connected(socket_id);
args.GetReturnValue().Set(Boolean::New(isolate, connected));
}
#ifdef WITH_SSL
void v8_runt_socket_enable_ssl(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsInt32()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked()));
return;
}
int socket_id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
bool result = runt_socket_enable_ssl(socket_id);
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "[V8] runt_socket_enable_ssl(%d) returned: %s", socket_id, result ? "true" : "false");
#endif
args.GetReturnValue().Set(Boolean::New(isolate, result));
}
#endif
void bind_socket_bridge(Isolate* isolate, const v8::Global<v8::Context>& context) {
// V8 locking
Locker locker{isolate};
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
Local<Context> current_context = Local<Context>::New(isolate, context);
Context::Scope context_scope(current_context);
Local<Object> global = current_context->Global();
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "[V8] Binding socket bridge functions to global context");
#endif
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_create").ToLocalChecked(),
Function::New(current_context, v8_runt_socket_create).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_bind").ToLocalChecked(),
Function::New(current_context, v8_runt_socket_bind).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_listen").ToLocalChecked(),
Function::New(current_context, v8_runt_socket_listen).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_accept").ToLocalChecked(),
Function::New(current_context, v8_runt_socket_accept).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_connect").ToLocalChecked(),
Function::New(current_context, v8_runt_socket_connect).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_send").ToLocalChecked(),
Function::New(current_context, v8_runt_socket_send).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_recv").ToLocalChecked(),
Function::New(current_context, v8_runt_socket_recv).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_close").ToLocalChecked(),
Function::New(current_context, v8_runt_socket_close).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_set_blocking").ToLocalChecked(),
Function::New(current_context, v8_runt_socket_set_blocking).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_select").ToLocalChecked(),
Function::New(current_context, v8_runt_socket_select).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_is_connected").ToLocalChecked(),
Function::New(current_context, v8_runt_socket_is_connected).ToLocalChecked());
#ifdef WITH_SSL
global->Set(current_context, String::NewFromUtf8(isolate, "runt_socket_enable_ssl").ToLocalChecked(),
Function::New(current_context, v8_runt_socket_enable_ssl).ToLocalChecked());
#endif
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <v8.h>
void bind_socket_bridge(v8::Isolate* isolate, const v8::Global<v8::Context>& context);
void v8_runt_socket_create(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_socket_bind(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_socket_listen(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_socket_accept(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_socket_connect(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_socket_send(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_socket_recv(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_socket_close(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_socket_set_blocking(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_socket_select(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_socket_is_connected(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifdef WITH_SSL
void v8_runt_socket_enable_ssl(const v8::FunctionCallbackInfo<v8::Value>& args);
#endif

79
Sources/thread_pool.h Normal file
View File

@ -0,0 +1,79 @@
#pragma once
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <atomic>
#include <future>
class ThreadPool {
private:
std::vector<std::thread> workers_;
std::queue<std::function<void()>> tasks_;
std::mutex queue_mutex_;
std::condition_variable condition_;
std::atomic<bool> stop_{false};
public:
explicit ThreadPool(size_t num_threads = std::thread::hardware_concurrency()) {
for (size_t i = 0; i < num_threads; ++i) {
workers_.emplace_back([this] {
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queue_mutex_);
condition_.wait(lock, [this] { return stop_ || !tasks_.empty(); });
if (stop_ && tasks_.empty()) return;
task = std::move(tasks_.front());
tasks_.pop();
}
task();
}
});
}
}
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args) -> std::future<typename std::invoke_result<F, Args...>::type> {
using return_type = typename std::invoke_result<F, Args...>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex_);
if (stop_) {
throw std::runtime_error("enqueue on stopped ThreadPool");
}
tasks_.emplace([task](){ (*task)(); });
}
condition_.notify_one();
return res;
}
size_t pending_tasks() const {
std::lock_guard<std::mutex> lock(const_cast<std::mutex&>(queue_mutex_));
return tasks_.size();
}
~ThreadPool() {
stop_ = true;
condition_.notify_all();
for (std::thread& worker : workers_) {
worker.join();
}
}
};

580
Sources/viewport_server.cpp Normal file
View File

@ -0,0 +1,580 @@
/**
* Viewport Server Implementation - Shared Memory Framebuffer Export
*
*/
#include "viewport_server.h"
#include <kinc/log.h>
#include <kinc/window.h>
#include <kinc/graphics4/graphics.h>
#include <kinc/graphics4/rendertarget.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#ifdef KORE_WINDOWS
#include <d3d11.h>
#include <dxgi.h>
// Forward declare structures from Kinc's Direct3D11.h
#define MAXIMUM_WINDOWS 16
struct dx_window {
HWND hwnd;
IDXGISwapChain *swapChain;
ID3D11Texture2D *backBuffer;
ID3D11RenderTargetView *renderTargetView;
ID3D11Texture2D *depthStencil;
ID3D11DepthStencilView *depthStencilView;
int width;
int height;
int new_width;
int new_height;
bool vsync;
int depth_bits;
int stencil_bits;
};
struct dx_context {
ID3D11Device *device;
ID3D11DeviceContext *context;
IDXGIDevice *dxgiDevice;
IDXGIAdapter *dxgiAdapter;
IDXGIFactory *dxgiFactory;
int current_window;
struct dx_window windows[MAXIMUM_WINDOWS];
};
// declare with C linkage to match Kinc C definition
extern "C" struct dx_context dx_ctx;
#endif
#ifdef KORE_WINDOWS
#include <Windows.h>
#else
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#endif
// global viewport server state
static ViewportServerState g_viewport_state = {0};
// ============================================================================
// Platform-specific Shared Memory Implementation
// ============================================================================
#ifdef KORE_WINDOWS
static bool shmem_create_windows(const char* name, size_t size) {
// Create file mapping
HANDLE hMapFile = CreateFileMappingA(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
(DWORD)(size >> 32),
(DWORD)(size & 0xFFFFFFFF),
name
);
if (hMapFile == NULL) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create shared memory: %lu", GetLastError());
return false;
}
void* pMem = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, size);
if (pMem == NULL) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to map shared memory: %lu", GetLastError());
CloseHandle(hMapFile);
return false;
}
g_viewport_state.shmem_handle = hMapFile;
g_viewport_state.shmem_ptr = pMem;
g_viewport_state.shmem_size = size;
SharedFramebufferHeader* header = (SharedFramebufferHeader*)pMem;
memset(header, 0, sizeof(SharedFramebufferHeader));
header->magic = VIEWPORT_MAGIC;
header->version = VIEWPORT_VERSION;
header->width = g_viewport_state.width;
header->height = g_viewport_state.height;
header->frame_id = 0;
header->ready_flag = 0;
header->format = 0; // RGBA8
kinc_log(KINC_LOG_LEVEL_INFO, "Windows shared memory created: %s (%zu bytes)", name, size);
return true;
}
static void shmem_destroy_windows(void) {
if (g_viewport_state.shmem_ptr) {
UnmapViewOfFile(g_viewport_state.shmem_ptr);
g_viewport_state.shmem_ptr = NULL;
}
if (g_viewport_state.shmem_handle) {
CloseHandle((HANDLE)g_viewport_state.shmem_handle);
g_viewport_state.shmem_handle = NULL;
}
}
#else
static bool shmem_create_posix(const char* name, size_t size) {
char shmem_name[256];
if (name[0] != '/') {
snprintf(shmem_name, sizeof(shmem_name), "/%s", name);
} else {
strncpy(shmem_name, name, sizeof(shmem_name) - 1);
}
// remove already existing if any
shm_unlink(shmem_name);
// shared memory object
int fd = shm_open(shmem_name, O_CREAT | O_RDWR, 0666);
if (fd == -1) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create shared memory: %s", strerror(errno));
return false;
}
if (ftruncate(fd, size) == -1) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to set shared memory size: %s", strerror(errno));
close(fd);
shm_unlink(shmem_name);
return false;
}
// map the memory
void* pMem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (pMem == MAP_FAILED) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to map shared memory: %s", strerror(errno));
close(fd);
shm_unlink(shmem_name);
return false;
}
g_viewport_state.shmem_handle = (void*)(intptr_t)fd;
g_viewport_state.shmem_ptr = pMem;
g_viewport_state.shmem_size = size;
SharedFramebufferHeader* header = (SharedFramebufferHeader*)pMem;
memset(header, 0, sizeof(SharedFramebufferHeader));
header->magic = VIEWPORT_MAGIC;
header->version = VIEWPORT_VERSION;
header->width = g_viewport_state.width;
header->height = g_viewport_state.height;
header->frame_id = 0;
header->ready_flag = 0;
header->format = 0; // RGBA8
kinc_log(KINC_LOG_LEVEL_INFO, "POSIX shared memory created: %s (%zu bytes)", shmem_name, size);
return true;
}
static void shmem_destroy_posix(void) {
if (g_viewport_state.shmem_ptr) {
munmap(g_viewport_state.shmem_ptr, g_viewport_state.shmem_size);
g_viewport_state.shmem_ptr = NULL;
}
if (g_viewport_state.shmem_handle) {
close((int)(intptr_t)g_viewport_state.shmem_handle);
g_viewport_state.shmem_handle = NULL;
// unlink shared memory
char shmem_name[256];
if (g_viewport_state.shmem_name[0] != '/') {
snprintf(shmem_name, sizeof(shmem_name), "/%s", g_viewport_state.shmem_name);
} else {
strncpy(shmem_name, g_viewport_state.shmem_name, sizeof(shmem_name) - 1);
}
shm_unlink(shmem_name);
}
}
#endif
static bool shmem_create(const char* name, size_t size) {
#ifdef KORE_WINDOWS
return shmem_create_windows(name, size);
#else
return shmem_create_posix(name, size);
#endif
}
static void shmem_destroy(void) {
#ifdef KORE_WINDOWS
shmem_destroy_windows();
#else
shmem_destroy_posix();
#endif
}
bool viewport_server_init(const char* shmem_name, int width, int height) {
if (g_viewport_state.initialized) {
kinc_log(KINC_LOG_LEVEL_WARNING, "Viewport server already initialized");
return true;
}
if (width <= 0 || width > VIEWPORT_MAX_WIDTH ||
height <= 0 || height > VIEWPORT_MAX_HEIGHT) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Invalid viewport dimensions: %dx%d", width, height);
return false;
}
g_viewport_state.width = width;
g_viewport_state.height = height;
strncpy(g_viewport_state.shmem_name, shmem_name ? shmem_name : VIEWPORT_SHMEM_NAME,
sizeof(g_viewport_state.shmem_name) - 1);
// pre-allocate shared memory for MAXIMUM size to avoid recreation on resize
// *** this prevents access denied errors when Blender still has the memory mapped
size_t max_pixel_size = (size_t)VIEWPORT_MAX_WIDTH * VIEWPORT_MAX_HEIGHT * 4; // RGBA8
size_t total_size = VIEWPORT_HEADER_SIZE + max_pixel_size;
// create shared memory
if (!shmem_create(g_viewport_state.shmem_name, total_size)) {
return false;
}
// now allocate CPU side pixel buffer for readback with a max size for the resize support
g_viewport_state.pixel_buffer = (uint8_t*)malloc(max_pixel_size);
if (!g_viewport_state.pixel_buffer) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to allocate pixel buffer");
shmem_destroy();
return false;
}
// TODO: needs investigation
// clear pixel buffer to prevent stale data from previous scenes
memset(g_viewport_state.pixel_buffer, 0, max_pixel_size);
// also clear shared memory pixel area to prevent flickering with old data
uint8_t* pixel_dest = (uint8_t*)g_viewport_state.shmem_ptr + VIEWPORT_HEADER_SIZE;
memset(pixel_dest, 0, max_pixel_size);
kinc_g4_render_target_t* rt = (kinc_g4_render_target_t*)malloc(sizeof(kinc_g4_render_target_t));
if (!rt) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to allocate render target");
free(g_viewport_state.pixel_buffer);
shmem_destroy();
return false;
}
kinc_g4_render_target_init(rt, width, height, KINC_G4_RENDER_TARGET_FORMAT_32BIT, 24, 0);
g_viewport_state.render_target = rt;
g_viewport_state.enabled = true;
g_viewport_state.initialized = true;
g_viewport_state.frame_count = 0;
kinc_log(KINC_LOG_LEVEL_INFO, "Viewport server initialized: %dx%d, shmem=%s",
width, height, g_viewport_state.shmem_name);
return true;
}
#ifdef KORE_WINDOWS
static ID3D11Texture2D* g_stagingTexture = NULL;
static int g_stagingWidth = 0, g_stagingHeight = 0;
#endif
void viewport_server_shutdown(void) {
if (!g_viewport_state.initialized) {
return;
}
#ifdef KORE_WINDOWS
if (g_stagingTexture) {
g_stagingTexture->Release();
g_stagingTexture = NULL;
g_stagingWidth = 0;
g_stagingHeight = 0;
}
#endif
if (g_viewport_state.render_target) {
kinc_g4_render_target_destroy((kinc_g4_render_target_t*)g_viewport_state.render_target);
free(g_viewport_state.render_target);
g_viewport_state.render_target = NULL;
}
if (g_viewport_state.pixel_buffer) {
free(g_viewport_state.pixel_buffer);
g_viewport_state.pixel_buffer = NULL;
}
shmem_destroy();
g_viewport_state.enabled = false;
g_viewport_state.initialized = false;
g_viewport_state.frame_count = 0;
kinc_log(KINC_LOG_LEVEL_INFO, "Viewport server shutdown complete");
}
bool viewport_server_is_enabled(void) {
return g_viewport_state.enabled && g_viewport_state.initialized;
}
void viewport_server_begin_frame(void) {
// no-op we let iron render normally to framebuffer and capture the backbuffer in end_frame BEFORE swap_buffers
}
void viewport_server_end_frame(void) {
if (!viewport_server_is_enabled()) return;
if (g_viewport_state.pixel_buffer == NULL ||
g_viewport_state.shmem_ptr == NULL) {
kinc_log(KINC_LOG_LEVEL_WARNING, "Viewport server: invalid state in end_frame");
g_viewport_state.frame_count++;
return;
}
if (g_viewport_state.frame_count < 3) {
g_viewport_state.frame_count++;
return;
}
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
header->width = g_viewport_state.width;
header->height = g_viewport_state.height;
size_t pixel_size = (size_t)g_viewport_state.width * g_viewport_state.height * 4;
uint8_t* pixels = g_viewport_state.pixel_buffer;
uint8_t* pixel_dest = (uint8_t*)g_viewport_state.shmem_ptr + VIEWPORT_HEADER_SIZE;
#ifdef KORE_WINDOWS
// Direct3D11 - read from backbuffer BEFORE swap_buffers
ID3D11DeviceContext* context = dx_ctx.context;
ID3D11Device* device = dx_ctx.device;
struct dx_window* window = &dx_ctx.windows[0];
ID3D11Texture2D* backBuffer = window->backBuffer;
if (backBuffer && context && device) {
D3D11_TEXTURE2D_DESC bbDesc;
backBuffer->GetDesc(&bbDesc);
int bbWidth = (int)bbDesc.Width;
int bbHeight = (int)bbDesc.Height;
// capture dimensions are the actual size of whats renderedso we clamp to max shared memory size
int captureWidth = bbWidth;
int captureHeight = bbHeight;
if (captureWidth > VIEWPORT_MAX_WIDTH) captureWidth = VIEWPORT_MAX_WIDTH;
if (captureHeight > VIEWPORT_MAX_HEIGHT) captureHeight = VIEWPORT_MAX_HEIGHT;
header->width = captureWidth;
header->height = captureHeight;
pixel_size = (size_t)captureWidth * captureHeight * 4;
// same dimensions between source and destination
if (!g_stagingTexture || g_stagingWidth != bbWidth || g_stagingHeight != bbHeight) {
if (g_stagingTexture) g_stagingTexture->Release();
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = bbWidth;
desc.Height = bbHeight;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = bbDesc.Format;
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_STAGING;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
HRESULT hr = device->CreateTexture2D(&desc, NULL, &g_stagingTexture);
if (FAILED(hr)) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create staging texture: 0x%08X", hr);
g_stagingTexture = NULL;
g_stagingWidth = 0;
g_stagingHeight = 0;
g_viewport_state.frame_count++;
return;
}
g_stagingWidth = bbWidth;
g_stagingHeight = bbHeight;
}
context->CopyResource(g_stagingTexture, backBuffer);
// only read the portion we need
D3D11_MAPPED_SUBRESOURCE mapped;
if (SUCCEEDED(context->Map(g_stagingTexture, 0, D3D11_MAP_READ, 0, &mapped))) {
for (int y = 0; y < captureHeight; y++) {
memcpy(pixels + y * captureWidth * 4,
(uint8_t*)mapped.pData + y * mapped.RowPitch,
captureWidth * 4);
}
context->Unmap(g_stagingTexture, 0);
// BGRA to RGBA conversion
for (size_t i = 0; i < pixel_size; i += 4) {
uint8_t t = pixels[i]; pixels[i] = pixels[i+2]; pixels[i+2] = t;
}
memcpy(pixel_dest, pixels, pixel_size);
}
}
#else
// other platforms use render target
if (g_viewport_state.render_target) {
kinc_g4_render_target_t* rt = (kinc_g4_render_target_t*)g_viewport_state.render_target;
kinc_g4_render_target_get_pixels(rt, pixels);
memcpy(pixel_dest, pixels, pixel_size);
}
#endif
g_viewport_state.frame_count++;
header->frame_id = g_viewport_state.frame_count;
// NOTE: Camera sync from RunT to Blender is handled via viewport_server_set_camera()
// which is called explicitly when RunT's internal camera changes.
// We do NOT extract camera from view_matrix here as that would create a feedback loop
// (Blender sends view_matrix -> we extract camera -> Blender applies it -> repeat)
// Memory barrier to ensure writes complete before setting ready flag
#ifdef KORE_WINDOWS
MemoryBarrier();
#else
__sync_synchronize();
#endif
header->ready_flag = 1;
}
bool viewport_server_check_resize(int* new_width, int* new_height) {
if (!viewport_server_is_enabled()) return false;
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
if (header->resize_request) {
*new_width = header->viewport_width;
*new_height = header->viewport_height;
header->resize_request = 0;
return true;
}
return false;
}
bool viewport_server_resize(int new_width, int new_height) {
if (!g_viewport_state.initialized) return false;
if (new_width <= 0 || new_width > VIEWPORT_MAX_WIDTH ||
new_height <= 0 || new_height > VIEWPORT_MAX_HEIGHT) {
kinc_log(KINC_LOG_LEVEL_ERROR, "Invalid resize dimensions: %dx%d", new_width, new_height);
return false;
}
if (new_width == g_viewport_state.width && new_height == g_viewport_state.height) {
return true;
}
kinc_log(KINC_LOG_LEVEL_INFO, "Viewport server resizing: %dx%d -> %dx%d",
g_viewport_state.width, g_viewport_state.height, new_width, new_height);
// resize the actual Kinc window so the D3D11 backbuffer is the correct size critical because viewport_server_end_frame captures from the backbuffer
kinc_window_resize(0, new_width, new_height);
if (g_viewport_state.render_target) {
kinc_g4_render_target_destroy((kinc_g4_render_target_t*)g_viewport_state.render_target);
// dont free here we reuse the allocation
}
// update dimensions pre-allocated and reinitialize render target with new size using same memory
g_viewport_state.width = new_width;
g_viewport_state.height = new_height;
kinc_g4_render_target_t* rt = (kinc_g4_render_target_t*)g_viewport_state.render_target;
if (rt) {
kinc_g4_render_target_init(rt, new_width, new_height, KINC_G4_RENDER_TARGET_FORMAT_32BIT, 24, 0);
}
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
if (header) {
header->width = new_width;
header->height = new_height;
header->ready_flag = 0;
}
kinc_log(KINC_LOG_LEVEL_INFO, "Viewport server resize complete");
return true;
}
bool viewport_server_check_shutdown(void) {
if (!viewport_server_is_enabled()) return false;
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
return header->shutdown_flag != 0;
}
bool viewport_server_get_view_matrix(float* matrix) {
if (!viewport_server_is_enabled() || !matrix) return false;
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
memcpy(matrix, header->view_matrix, 16 * sizeof(float));
return true;
}
bool viewport_server_get_proj_matrix(float* matrix) {
if (!viewport_server_is_enabled() || !matrix) return false;
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
memcpy(matrix, header->proj_matrix, 16 * sizeof(float));
return true;
}
ViewportServerState* viewport_server_get_state(void) {
return &g_viewport_state;
}
void viewport_server_set_camera(float pos_x, float pos_y, float pos_z,
float rot_x, float rot_y, float rot_z, float rot_w) {
if (!viewport_server_is_enabled()) return;
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
if (!header) return;
header->runt_camera_pos[0] = pos_x;
header->runt_camera_pos[1] = pos_y;
header->runt_camera_pos[2] = pos_z;
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->runt_camera_dirty = 1;
}
bool viewport_server_read_input(InputEvent* event) {
if (!viewport_server_is_enabled()) return false;
if (!event) return false;
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
if (!header) return false;
uint32_t read_idx = header->input_read_idx;
uint32_t write_idx = header->input_write_idx;
if (read_idx == write_idx) {
return false;
}
*event = header->input_events[read_idx % MAX_INPUT_EVENTS];
header->input_read_idx = (read_idx + 1) % MAX_INPUT_EVENTS;
return true;
}
bool viewport_server_input_enabled(void) {
if (!viewport_server_is_enabled()) return false;
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
if (!header) return false;
return header->input_enabled != 0;
}

122
Sources/viewport_server.h Normal file
View File

@ -0,0 +1,122 @@
/**
* Viewport Server - Shared Memory Framebuffer Export for Blender Integration
*
* This module provides headless rendering with framebuffer export via shared memory
* for integration with Blender's viewport as an external render engine.
*
* Protocol:
* - RunT renders to offscreen render target
* - Pixels are exported to shared memory each frame
* - Blender Python addon reads shared memory and displays in viewport
*/
#ifndef VIEWPORT_SERVER_H
#define VIEWPORT_SERVER_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define INPUT_EVENT_NONE 0
#define INPUT_EVENT_MOUSE_MOVE 1
#define INPUT_EVENT_MOUSE_DOWN 2
#define INPUT_EVENT_MOUSE_UP 3
#define INPUT_EVENT_KEY_DOWN 4
#define INPUT_EVENT_KEY_UP 5
#define INPUT_EVENT_MOUSE_WHEEL 6
#define MOUSE_BUTTON_LEFT 0
#define MOUSE_BUTTON_RIGHT 1
#define MOUSE_BUTTON_MIDDLE 2
#define MAX_INPUT_EVENTS 32
#pragma pack(push, 1)
typedef struct {
uint8_t type; // INPUT_EVENT_* type
uint8_t button; // mouse button or key code
int16_t x; // mouse X position or delta
int16_t y; // mouse Y position or delta
int16_t delta; // scroll wheel delta
uint32_t modifiers; // shift/ctrl/alt flags
} InputEvent;
#pragma pack(pop)
// shared memory header structure must match Python end
#pragma pack(push, 1)
typedef struct {
uint32_t magic; // 0x52554E54 = 'RUNT'
uint32_t version; // protocol version (2)
uint32_t width; // current frame width
uint32_t height; // current frame height
uint64_t frame_id; // incrementing frame counter
uint32_t ready_flag; // 1 = new frame ready, 0 = consumed by blender
uint32_t format; // 0 = RGBA8, 1 = BGRA8
uint32_t viewport_width; // requested viewport width from Blender
uint32_t viewport_height; // requested viewport height from Blender
uint32_t resize_request; // 1 = blender requests resize
uint32_t shutdown_flag; // 1 = blender requests shutdown
uint32_t input_enabled; // 1 = blender is capturing input for RunT
float view_matrix[16]; // view matrix from blender column-major
float proj_matrix[16]; // projection matrix from blender column-major
// camera output from RunT to Blender bidirectional sync
float runt_camera_pos[3]; // camera position from RunT (x, y, z)
float runt_camera_rot[4]; // camera rotation from RunT quaternion: x, y, z, w
uint32_t runt_camera_dirty; // 1 = RunT updated camera, 0 = consumed by blender
// input event queue from blender to RunT
uint32_t input_write_idx; // write index Blender increments
uint32_t input_read_idx; // read index RunT increments
InputEvent input_events[MAX_INPUT_EVENTS]; // ring buffer of input events
// pixel data follows header width * height * 4 bytes for RGBA8
} SharedFramebufferHeader;
#pragma pack(pop)
#define VIEWPORT_MAGIC 0x52554E54
#define VIEWPORT_VERSION 2
#define VIEWPORT_HEADER_SIZE sizeof(SharedFramebufferHeader)
// default shared memory name
#define VIEWPORT_SHMEM_NAME "RUNT_VIEWPORT_FB"
#define VIEWPORT_MAX_WIDTH 4096
#define VIEWPORT_MAX_HEIGHT 4096
// viewport server state
typedef struct {
bool enabled;
bool initialized;
int width;
int height;
char shmem_name[256];
uint64_t frame_count;
void* render_target; // kinc_g4_render_target_t*
uint8_t* pixel_buffer; // CPU-side pixel buffer
void* shmem_handle; // platform-specific handle
void* shmem_ptr; // mapped memory pointer
size_t shmem_size; // total shared memory size
} ViewportServerState;
bool viewport_server_init(const char* shmem_name, int width, int height);
void viewport_server_shutdown(void);
bool viewport_server_is_enabled(void);
void viewport_server_begin_frame(void);
void viewport_server_end_frame(void);
bool viewport_server_check_resize(int* new_width, int* new_height);
bool viewport_server_resize(int new_width, int new_height);
bool viewport_server_check_shutdown(void);
bool viewport_server_get_view_matrix(float* matrix);
bool viewport_server_get_proj_matrix(float* matrix);
ViewportServerState* viewport_server_get_state(void);
void viewport_server_set_camera(float pos_x, float pos_y, float pos_z, float rot_x, float rot_y, float rot_z, float rot_w);
bool viewport_server_read_input(InputEvent* event);
bool viewport_server_input_enabled(void);
#ifdef __cplusplus
}
#endif
#endif

2865
Sources/websocket.cpp Normal file

File diff suppressed because it is too large Load Diff

199
Sources/websocket.h Normal file
View File

@ -0,0 +1,199 @@
#pragma once
#ifdef WITH_NETWORKING
#include "websocket_config.h"
#include "lockfree_queue.h"
#include "global_thread_pool.h"
#include <kinc/network/socket.h>
#include <functional>
#include <string>
#include <vector>
#include <atomic>
#include <thread>
#include <v8.h>
#include <memory>
#ifdef _WIN32
typedef void* SOCKET_HANDLE;
#else
typedef int SOCKET_HANDLE;
#endif
#ifdef WITH_SSL
#ifndef _WIN32
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/crypto.h>
#endif
#endif
namespace WebSocketWrapper {
enum ReadyState {
CONNECTING = 0,
OPEN = 1,
CLOSING = 2,
CLOSED = 3
};
enum EventType {
EVENT_OPEN,
EVENT_MESSAGE,
EVENT_ERROR,
EVENT_CLOSE
};
struct WebSocketEvent {
EventType type;
std::string data;
int code;
std::string reason;
WebSocketEvent() : type(EVENT_OPEN), code(0) {}
WebSocketEvent(EventType t) : type(t), code(0) {}
WebSocketEvent(EventType t, const std::string& d) : type(t), data(d), code(0) {}
WebSocketEvent(EventType t, int c, const std::string& r) : type(t), code(c), reason(r) {}
};
class WebSocketClient {
public:
WebSocketClient(v8::Isolate* isolate, v8::Global<v8::Context>* global_context, const std::string& url);
~WebSocketClient();
// this is for browser compatible methods
void send(const std::string& data);
void sendBinary(const std::string& data);
void close(int code = 1000, const std::string& reason = "");
// process pending events on main thread
void processEvents();
ReadyState getReadyState() const { return ready_state_; }
const std::string& getUrl() const { return url_; }
const std::string& getProtocol() const { return protocol_; }
int getBufferedAmount() const { return buffered_amount_; }
// V8 callback setters
void setOnOpen(v8::Local<v8::Function> callback);
void setOnMessage(v8::Local<v8::Function> callback);
void setOnError(v8::Local<v8::Function> callback);
void setOnClose(v8::Local<v8::Function> callback);
private:
v8::Isolate* isolate_;
v8::Global<v8::Context>* global_context_;
std::string url_;
std::string protocol_;
ReadyState ready_state_;
bool is_ssl_;
int buffered_amount_;
// V8 callback storage
v8::Global<v8::Function> on_open_;
v8::Global<v8::Function> on_message_;
v8::Global<v8::Function> on_error_;
v8::Global<v8::Function> on_close_;
LockFreeQueue<WebSocketEvent> event_queue_;
void* ws_;
void* ssl_ws_;
#ifdef WITH_SSL
#ifdef _WIN32
// windows SChannel opaque pointers
void* ssl_cred_handle_;
void* ssl_context_handle_;
bool ssl_context_initialized_;
int ssl_socket_;
std::vector<char> ssl_buffer_;
#else
SSL_CTX* ssl_ctx_;
SSL* ssl_;
bool ssl_initialized_;
#endif
bool initWSL();
void cleanupWSL();
bool performWSLHandshake(int socket, const std::string& host);
int wslRead(char* buffer, int length);
int webSocketSSLSend(const void* data, int len);
int webSocketSSLSend(const char* buffer, int length);
int webSocketSSLReceive(char* buffer, int bufferSize);
#endif
std::string base64Encode(const std::string& data);
// called from worker thread
void handleOpen();
void handleMessage(const std::string& message);
void handleError(const std::string& error);
void handleClose(int code, const std::string& reason);
// called from main thread
void processOpenEvent();
void processMessageEvent(const std::string& message);
void processMessageBatch(const std::vector<std::string>& messages);
void processErrorEvent(const std::string& error);
void processCloseEvent(int code, const std::string& reason);
// main thread only
void callCallback(v8::Global<v8::Function>& callback, int argc, v8::Local<v8::Value> argv[]);
bool connectToServer(const std::string& host, int port, const std::string& path);
void messageLoop();
void processFrame(uint8_t opcode, bool fin, const std::vector<uint8_t>& payload);
// RFC 6455 protocol
bool performWebSocketHandshake(int sock, const std::string& host, int port, const std::string& path);
void fireOpenEvent();
void fireMessageEvent(const std::string& message, bool binary = false);
void fireErrorEvent(const std::string& error);
void fireCloseEvent(int code, const std::string& reason);
bool parseWebSocketFrame(const std::vector<uint8_t>& buffer, size_t& offset);
std::vector<uint8_t> createWebSocketFrame(const std::string& message, uint8_t opcode = 0x1);
std::vector<uint8_t> createWebSocketBinaryFrame(const std::string& data);
std::vector<uint8_t> createCloseFrame(uint16_t code, const std::string& reason);
std::vector<uint8_t> createPongFrame(const std::vector<uint8_t>& payload);
std::string generateWebSocketKey();
std::string base64Encode(const std::vector<uint8_t>& data);
std::vector<uint8_t> sha1Hash(const std::string& data);
void maskData(std::vector<uint8_t>& data, uint32_t maskKey);
std::vector<uint8_t> partial_frame_buffer_;
bool expecting_continuation_ = false;
uint8_t continuation_opcode_ = 0;
bool initializeWebSocketSSL(int sock, const std::string& host);
void cleanupWebSocketSSL();
bool parseUrl(const std::string& url, std::string& host, int& port, std::string& path);
};
void initialize();
void cleanup();
// called from main event loop
void processEvents();
int createWebSocketConnection(v8::Isolate* isolate, const std::string& url);
// get global context from main.cpp
v8::Global<v8::Context>* getGlobalContext();
}
// V8 binding functions called from main.cpp
void runt_websocket_create(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_websocket_send(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_websocket_send_binary(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_websocket_close(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_websocket_get_ready_state(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_websocket_get_url(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_websocket_get_buffered_amount(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_websocket_set_onopen(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_websocket_set_onmessage(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_websocket_set_onerror(const v8::FunctionCallbackInfo<v8::Value>& args);
void runt_websocket_set_onclose(const v8::FunctionCallbackInfo<v8::Value>& args);
void createWebSocketClass(v8::Isolate* isolate, v8::Local<v8::ObjectTemplate>& global);
void createWebSocketEventClasses(v8::Isolate* isolate, v8::Local<v8::ObjectTemplate>& global);
#endif

1249
Sources/websocket_bridge.cpp Normal file

File diff suppressed because it is too large Load Diff

160
Sources/websocket_bridge.h Normal file
View File

@ -0,0 +1,160 @@
#pragma once
#include <kinc/network/socket.h>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <map>
#include <memory>
#include "broadcast_queue.h"
#include "async_engine.h"
#include "ring_buffer.h"
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <mswsock.h>
#include <windows.h>
#endif
enum class IOCPOperationType {
IOCP_ACCEPT,
IOCP_RECV,
IOCP_SEND
};
// 64KB buffer for recv/send
struct IOCPOverlapped {
#ifdef _WIN32
OVERLAPPED overlapped;
WSABUF wsaBuf;
#endif
IOCPOperationType opType;
int clientId;
uint8_t buffer[65536];
size_t bufferLen;
};
class RunTWebSocketServer;
struct RunTWebSocketClient;
// client with ring buffer
struct RunTWebSocketClient {
kinc_socket_t socket;
int clientId;
std::atomic<bool> connected{true};
std::atomic<bool> handshakeCompleted{false};
std::atomic<bool> nonBlockingMode{false};
unsigned remoteAddress, remotePort;
std::string handshakeBuffer;
// 64KB default with a power of 2
std::unique_ptr<RingBuffer> recvRingBuffer;
std::vector<uint8_t> sendQueue;
std::atomic<size_t> queuedBytes{0};
std::atomic<bool> sendPending{false};
std::mutex sendQueueMutex;
std::unique_ptr<IOCPOverlapped> recvOverlapped;
std::unique_ptr<IOCPOverlapped> sendOverlapped;
// 256KB for high throughput
RunTWebSocketClient(int id, kinc_socket_t sock)
: clientId(id), socket(sock), remoteAddress(0), remotePort(0),
recvRingBuffer(std::make_unique<RingBuffer>(262144)),
recvOverlapped(std::make_unique<IOCPOverlapped>()),
sendOverlapped(std::make_unique<IOCPOverlapped>()) {
recvOverlapped->opType = IOCPOperationType::IOCP_RECV;
recvOverlapped->clientId = id;
sendOverlapped->opType = IOCPOperationType::IOCP_SEND;
sendOverlapped->clientId = id;
}
};
class RunTWebSocketServer {
public:
int serverId;
std::string host;
int port;
int maxConnections;
kinc_socket_t serverSocket;
std::atomic<bool> isRunning;
// lock free no mutex
static const size_t MAX_CLIENTS = 1024;
std::atomic<RunTWebSocketClient*> clients[MAX_CLIENTS];
std::atomic<size_t> clientCount{0};
std::atomic<bool> running;
std::thread serverThread;
LockFreeBroadcastQueue broadcastQueue;
std::thread broadcastWorker;
std::atomic<bool> broadcastRunning{false};
#ifdef _WIN32
// IOCP handle for events no polling
HANDLE iocpHandle = NULL;
std::thread iocpThread;
void iocpEventLoop();
void postRecv(RunTWebSocketClient* client);
void postSend(RunTWebSocketClient* client, const uint8_t* data, size_t len);
void onRecvComplete(RunTWebSocketClient* client, DWORD bytesTransferred);
void onSendComplete(RunTWebSocketClient* client, DWORD bytesTransferred);
#endif
RunTWebSocketServer(int id, const std::string& h, int p, int maxConn);
~RunTWebSocketServer();
bool start();
void stop();
void tick();
void sendToAll(const std::string& data);
void broadcastBinaryFrame(const std::string& binaryData, int excludeClientId);
void broadcastFrameWithOpcode(const std::string& data, uint8_t opcode, int excludeClientId);
void broadcastRawFrame(const std::string& rawFrame, int excludeClientId);
void sendToClient(int clientId, const std::string& data);
std::string generateWebSocketAccept(const std::string& key);
void processWebSocketHandshake(RunTWebSocketClient* client);
void processWebSocketFrames(RunTWebSocketClient* client);
void processClientData(RunTWebSocketClient* client);
void broadcastWorkerLoop();
std::vector<uint8_t> createWebSocketFrame(const std::string& data, uint8_t opcode);
void sendFrameToClients(const std::vector<uint8_t>& frame, int excludeClientId);
private:
void serverLoop();
void acceptClients();
};
// global server
extern std::map<int, RunTWebSocketServer*> g_websocket_servers;
extern std::mutex g_servers_mutex;
extern int g_next_server_id;
// native bridge functions exposed to V8/JavaScript
extern "C" {
int runt_websocket_server_create(const char* host, int port, int maxConnections);
bool runt_websocket_server_start(int serverId);
void runt_websocket_server_stop(int serverId);
void runt_websocket_server_tick(int serverId);
void runt_websocket_server_send_all(int serverId, const char* data);
void runt_websocket_server_send_all_binary(int serverId, const char* data, size_t length);
void runt_websocket_server_send_client(int serverId, int clientId, const char* data);
void runt_websocket_server_send_client_binary(int serverId, int clientId, const char* data, size_t length);
}

View File

@ -0,0 +1,43 @@
#ifndef WEBSOCKET_CONFIG_H
#define WEBSOCKET_CONFIG_H
#ifdef WITH_UWS
// uWebSockets implementation
// disable compression to avoid zlib dependency
// #define UWS_NO_ZLIB
#include "uws_websocket_bridge.h"
#include "uws_websocket_v8_bindings.h"
#define WEBSOCKET_BRIDGE_PREFIX "uws_"
#define WEBSOCKET_LOG_PREFIX "[UWS]"
#define WEBSOCKET_BIND_V8(isolate, context) bind_uws_websocket_bridge(isolate, context)
#else
// native WebSocket implementation
#include "socket_bridge.h"
#include "socket_v8_bindings.h"
#include "websocket_bridge.h"
#include "websocket_v8_bindings.h"
#define WEBSOCKET_BRIDGE_PREFIX "Native_"
#define WEBSOCKET_LOG_PREFIX "[NATIVE]"
#define WEBSOCKET_BIND_V8(isolate, context) do { \
bind_socket_bridge(isolate, context); \
bind_websocket_bridge(isolate, context); \
} while(0)
#endif
#ifdef WITH_UWS
#define WEBSOCKET_SERVER_CREATE(host, port, maxConn) uws_websocket_server_create(host, port, maxConn)
#define WEBSOCKET_SERVER_DESTROY(id) uws_websocket_server_destroy(id)
#define WEBSOCKET_SERVER_SEND_TO_ALL(id, data) uws_websocket_server_send_to_all(id, data)
#define WEBSOCKET_SERVER_SEND_TO_CLIENT(id, clientId, data) uws_websocket_server_send_to_client(id, clientId, data)
#define WEBSOCKET_SERVER_TICK(id) uws_websocket_server_tick(id)
#else
#define WEBSOCKET_SERVER_CREATE(host, port, maxConn) runt_websocket_server_create(host, port, maxConn)
#define WEBSOCKET_SERVER_DESTROY(id) runt_websocket_server_destroy(id)
#define WEBSOCKET_SERVER_SEND_TO_ALL(id, data) runt_websocket_server_send_to_all(id, data)
#define WEBSOCKET_SERVER_SEND_TO_CLIENT(id, clientId, data) runt_websocket_server_send_to_client(id, clientId, data)
#define WEBSOCKET_SERVER_TICK(id) runt_websocket_server_tick(id)
#endif
#endif

View File

@ -0,0 +1,230 @@
#include "websocket_v8_bindings.h"
#include "websocket_bridge.h"
#include <kinc/log.h>
using namespace v8;
void v8_runt_websocket_server_create(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 3 || !args[0]->IsString() || !args[1]->IsNumber() || !args[2]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "runt_websocket_server_create requires (host, port, maxConnections)").ToLocalChecked()));
return;
}
String::Utf8Value host(isolate, args[0]);
int port = args[1]->Int32Value(isolate->GetCurrentContext()).FromJust();
int maxConnections = args[2]->Int32Value(isolate->GetCurrentContext()).FromJust();
int serverId = runt_websocket_server_create(*host, port, maxConnections);
args.GetReturnValue().Set(Integer::New(isolate, serverId));
}
void v8_runt_websocket_server_start(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "runt_websocket_server_start requires serverId").ToLocalChecked()));
return;
}
int serverId = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
bool success = runt_websocket_server_start(serverId);
args.GetReturnValue().Set(Boolean::New(isolate, success));
}
void v8_runt_websocket_server_stop(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "runt_websocket_server_stop requires serverId").ToLocalChecked()));
return;
}
int serverId = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
runt_websocket_server_stop(serverId);
args.GetReturnValue().Set(Undefined(isolate));
}
void v8_runt_websocket_server_tick(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "runt_websocket_server_tick requires serverId").ToLocalChecked()));
return;
}
int serverId = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
runt_websocket_server_tick(serverId);
args.GetReturnValue().Set(Undefined(isolate));
}
void v8_runt_websocket_server_send_all(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 2 || !args[0]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "runt_websocket_server_send_all requires (serverId, data)").ToLocalChecked()));
return;
}
int serverId = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
if (args[1]->IsArrayBuffer()) {
Local<ArrayBuffer> buffer = args[1].As<ArrayBuffer>();
void* data = buffer->GetBackingStore()->Data();
size_t length = buffer->ByteLength();
runt_websocket_server_send_all_binary(serverId, static_cast<const char*>(data), length);
} else if (args[1]->IsArrayBufferView()) {
Local<ArrayBufferView> view = args[1].As<ArrayBufferView>();
Local<ArrayBuffer> buffer = view->Buffer();
void* data = static_cast<char*>(buffer->GetBackingStore()->Data()) + view->ByteOffset();
size_t length = view->ByteLength();
runt_websocket_server_send_all_binary(serverId, static_cast<const char*>(data), length);
} else if (args[1]->IsString()) {
String::Utf8Value data(isolate, args[1]);
runt_websocket_server_send_all(serverId, *data);
} else {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "runt_websocket_server_send_all data must be String or ArrayBuffer").ToLocalChecked()));
return;
}
args.GetReturnValue().Set(Undefined(isolate));
}
//#ifdef WITH_BENCHMARK
//extern "C" {
// void websocket_run_groupchat_test(int rate, int duration);
// void websocket_run_benchmark();
//}
//void v8_websocket_run_test(const FunctionCallbackInfo<Value>& args) {
// Isolate* isolate = args.GetIsolate();
// int rate = 60, duration = 5;
// if (args.Length() >= 1 && args[0]->IsNumber()) {
// rate = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
// }
// if (args.Length() >= 2 && args[1]->IsNumber()) {
// duration = args[1]->Int32Value(isolate->GetCurrentContext()).FromJust();
// }
// websocket_run_groupchat_test(rate, duration);
//}
//void v8_websocket_run_benchmark(const FunctionCallbackInfo<Value>& args) {
// websocket_run_benchmark();
//}
//#endif
void v8_runt_websocket_server_send_client(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 3 || !args[0]->IsNumber() || !args[1]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "runt_websocket_server_send_client requires (serverId, clientId, data)").ToLocalChecked()));
return;
}
int serverId = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
int clientId = args[1]->Int32Value(isolate->GetCurrentContext()).FromJust();
if (args[2]->IsArrayBuffer()) {
Local<ArrayBuffer> buffer = args[2].As<ArrayBuffer>();
void* data = buffer->GetBackingStore()->Data();
size_t length = buffer->ByteLength();
runt_websocket_server_send_client_binary(serverId, clientId, static_cast<const char*>(data), length);
} else if (args[2]->IsArrayBufferView()) {
Local<ArrayBufferView> view = args[2].As<ArrayBufferView>();
Local<ArrayBuffer> buffer = view->Buffer();
void* data = static_cast<char*>(buffer->GetBackingStore()->Data()) + view->ByteOffset();
size_t length = view->ByteLength();
runt_websocket_server_send_client_binary(serverId, clientId, static_cast<const char*>(data), length);
} else if (args[2]->IsString()) {
String::Utf8Value data(isolate, args[2]);
runt_websocket_server_send_client(serverId, clientId, *data);
} else {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "runt_websocket_server_send_client data must be String or ArrayBuffer").ToLocalChecked()));
return;
}
args.GetReturnValue().Set(Undefined(isolate));
}
void bind_websocket_bridge(Isolate* isolate, const Global<Context>& context) {
// TODO: thread locking
Locker locker{isolate};
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
Local<Context> current_context = Local<Context>::New(isolate, context);
Context::Scope context_scope(current_context);
Local<Object> global = current_context->Global();
Local<Object> runt_obj;
Local<Value> existing_runt = global->Get(current_context, String::NewFromUtf8(isolate, "RunT").ToLocalChecked()).ToLocalChecked();
if (existing_runt->IsObject()) {
runt_obj = existing_runt->ToObject(current_context).ToLocalChecked();
} else {
runt_obj = Object::New(isolate);
global->Set(current_context, String::NewFromUtf8(isolate, "RunT").ToLocalChecked(), runt_obj);
}
runt_obj->Set(current_context, String::NewFromUtf8(isolate, "websocketServerCreate").ToLocalChecked(),
Function::New(current_context, v8_runt_websocket_server_create).ToLocalChecked());
runt_obj->Set(current_context, String::NewFromUtf8(isolate, "websocketServerStart").ToLocalChecked(),
Function::New(current_context, v8_runt_websocket_server_start).ToLocalChecked());
runt_obj->Set(current_context, String::NewFromUtf8(isolate, "websocketServerStop").ToLocalChecked(),
Function::New(current_context, v8_runt_websocket_server_stop).ToLocalChecked());
runt_obj->Set(current_context, String::NewFromUtf8(isolate, "websocketServerTick").ToLocalChecked(),
Function::New(current_context, v8_runt_websocket_server_tick).ToLocalChecked());
runt_obj->Set(current_context, String::NewFromUtf8(isolate, "websocketServerSendAll").ToLocalChecked(),
Function::New(current_context, v8_runt_websocket_server_send_all).ToLocalChecked());
runt_obj->Set(current_context, String::NewFromUtf8(isolate, "websocketServerSendClient").ToLocalChecked(),
Function::New(current_context, v8_runt_websocket_server_send_client).ToLocalChecked());
// uWS compatibility aliases
//runt_obj->Set(current_context, String::NewFromUtf8(isolate, "uwsWebsocketServerCreate").ToLocalChecked(),
// Function::New(current_context, v8_runt_websocket_server_create).ToLocalChecked());
//runt_obj->Set(current_context, String::NewFromUtf8(isolate, "uwsWebsocketServerDestroy").ToLocalChecked(),
// Function::New(current_context, v8_runt_websocket_server_stop).ToLocalChecked());
//runt_obj->Set(current_context, String::NewFromUtf8(isolate, "uwsWebsocketServerSend").ToLocalChecked(),
// Function::New(current_context, v8_runt_websocket_server_send_all).ToLocalChecked());
// runt_obj->Set(current_context, String::NewFromUtf8(isolate, "uwsWebsocketServerTick").ToLocalChecked(),
// Function::New(current_context, v8_runt_websocket_server_tick).ToLocalChecked());
//runt_obj->Set(current_context, String::NewFromUtf8(isolate, "uwsWebsocketServerSetOnConnection").ToLocalChecked(),
// Function::New(current_context, v8_runt_websocket_server_start).ToLocalChecked());
//runt_obj->Set(current_context, String::NewFromUtf8(isolate, "uwsWebsocketServerSetOnMessage").ToLocalChecked(),
// Function::New(current_context, v8_runt_websocket_server_start).ToLocalChecked());
//runt_obj->Set(current_context, String::NewFromUtf8(isolate, "uwsWebsocketServerSetOnDisconnection").ToLocalChecked(),
// Function::New(current_context, v8_runt_websocket_server_stop).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_websocket_server_create").ToLocalChecked(),
Function::New(current_context, v8_runt_websocket_server_create).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_websocket_server_start").ToLocalChecked(),
Function::New(current_context, v8_runt_websocket_server_start).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_websocket_server_stop").ToLocalChecked(),
Function::New(current_context, v8_runt_websocket_server_stop).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_websocket_server_tick").ToLocalChecked(),
Function::New(current_context, v8_runt_websocket_server_tick).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_websocket_server_send_all").ToLocalChecked(),
Function::New(current_context, v8_runt_websocket_server_send_all).ToLocalChecked());
global->Set(current_context, String::NewFromUtf8(isolate, "runt_websocket_server_send_client").ToLocalChecked(),
Function::New(current_context, v8_runt_websocket_server_send_client).ToLocalChecked());
//#ifdef WITH_BENCHMARK
// global->Set(current_context, String::NewFromUtf8(isolate, "websocket_run_test").ToLocalChecked(),
// Function::New(current_context, v8_websocket_run_test).ToLocalChecked());
// global->Set(current_context, String::NewFromUtf8(isolate, "websocket_run_benchmark").ToLocalChecked(),
// Function::New(current_context, v8_websocket_run_benchmark).ToLocalChecked());
//#endif
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <v8.h>
void bind_websocket_bridge(v8::Isolate* isolate, const v8::Global<v8::Context>& context);
void v8_runt_websocket_server_create(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_websocket_server_start(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_websocket_server_stop(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_websocket_server_tick(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_websocket_server_send_all(const v8::FunctionCallbackInfo<v8::Value>& args);
void v8_runt_websocket_server_send_client(const v8::FunctionCallbackInfo<v8::Value>& args);

View File

@ -131,7 +131,7 @@ namespace {
return;
}
if (args.Length() > 1) {
kinc_log(KINC_LOG_LEVEL_WARNING, "Krom workers only support 1 argument for postMessage");
kinc_log(KINC_LOG_LEVEL_WARNING, "RunT workers only support 1 argument for postMessage");
}
Local<External> external = Local<External>::Cast(args.Data());
@ -374,7 +374,7 @@ namespace {
return;
}
if (args.Length() > 1) {
kinc_log(KINC_LOG_LEVEL_WARNING, "Krom workers only support 1 argument for postMessage");
kinc_log(KINC_LOG_LEVEL_WARNING, "RunT workers only support 1 argument for postMessage");
}
Local<External> external = Local<External>::Cast(args.This()->GetInternalField(0));
@ -422,7 +422,7 @@ namespace {
}
if (args.Length() > 1) {
kinc_log(KINC_LOG_LEVEL_WARNING, "Krom only supports one argument for worker constructor, ignoring extra arguments");
kinc_log(KINC_LOG_LEVEL_WARNING, "RunT only supports one argument for worker constructor, ignoring extra arguments");
}
WorkerMessagePort* messagePort = new WorkerMessagePort;
@ -472,6 +472,8 @@ namespace {
}
void bind_worker_class(Isolate* isolate, const v8::Global<v8::Context>& context) {
// TODO: verify lock
Locker locker{isolate};
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
@ -495,11 +497,18 @@ void bind_worker_class(Isolate* isolate, const v8::Global<v8::Context>& context)
}
void handle_worker_messages(v8::Isolate* isolate, const v8::Global<v8::Context>& context) {
Isolate::Scope isolate_scope(isolate);
// TODO: re-investigate
if (!isolate) {
return;
}
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
ContextData* context_data = (ContextData*)(isolate->GetData(worker_data_slot));
if (!context_data) {
return;
}
if (context_data->workers.size() == 0) {
return;
}

View File

@ -2,12 +2,16 @@ const fs = require('fs');
const path = require('path');
let flags = {
name: 'Leenkx Core',
name: 'Leenkx-RunT',
package: 'org.leenkx',
release: process.argv.indexOf("--debug") == -1,
with_audio: true,
with_worker: true,
with_compute: true
with_compute: true,
with_networking: true,
with_viewport: true,
with_uws: false,
debug_network: false
};
const system = platform === Platform.Windows ? "win32" :
@ -16,7 +20,7 @@ const system = platform === Platform.Windows ? "win32" :
platform === Platform.Wasm ? "wasm" :
platform === Platform.Android ? "android" :
platform === Platform.iOS ? "ios" :
"unknown";
"unknown";
const build = flags.release ? 'release' : 'debug';
let root = __dirname;
@ -39,8 +43,72 @@ if (flags.with_compute) {
project.addDefine('WITH_COMPUTE');
}
if (flags.with_networking) {
project.addDefine('WITH_NETWORKING');
project.addDefine('WITH_SSL');
}
if (flags.with_uws) {
project.addDefine('WITH_UWS');
}
if (flags.debug_network) {
project.addDefine('DEBUG_NETWORK');
}
if (flags.with_viewport && (platform === Platform.Windows || platform === Platform.Linux || platform === Platform.OSX)) {
project.addDefine('WITH_VIEWPORT');
}
project.addFile('Sources/main.cpp');
if(flags.with_networking){
if (flags.with_uws) {
project.addIncludeDir('uWebSockets/uSockets/src');
project.addFile('Sources/uws_websocket_bridge.h');
project.addFile('Sources/uws_websocket_bridge.cpp');
project.addFile('Sources/uws_websocket_v8_bindings.h');
project.addFile('Sources/uws_websocket_v8_bindings.cpp');
project.addFile('uWebSockets/uSockets/src/bsd.c');
project.addFile('uWebSockets/uSockets/src/context.c');
project.addFile('uWebSockets/uSockets/src/loop.c');
project.addFile('uWebSockets/uSockets/src/socket.c');
project.addFile('uWebSockets/uSockets/src/udp.c');
} else {
project.addFile('Sources/async_engine.h');
project.addFile('Sources/async_engine.cpp');
project.addFile('Sources/connection_pool.h');
project.addFile('Sources/connection_pool.cpp');
project.addFile('Sources/global_thread_pool.h');
project.addFile('Sources/global_thread_pool.cpp');
project.addFile('Sources/websocket_v8_bindings.h');
project.addFile('Sources/websocket_v8_bindings.cpp');
project.addFile('Sources/websocket_bridge.h');
project.addFile('Sources/websocket_bridge.cpp');
project.addFile('Sources/websocket_config.h');
project.addFile('Sources/websocket.h');
project.addFile('Sources/websocket.cpp');
project.addFile('Sources/thread_pool.h');
project.addFile('Sources/socket_v8_bindings.h');
project.addFile('Sources/socket_v8_bindings.cpp');
project.addFile('Sources/socket_optimization.h');
project.addFile('Sources/socket_bridge.h');
project.addFile('Sources/socket_bridge.cpp');
project.addFile('Sources/lockfree_queue.h');
project.addFile('Sources/httprequest.h');
project.addFile('Sources/httprequest.cpp');
}
}
if (flags.with_viewport && (platform === Platform.Windows || platform === Platform.Linux || platform === Platform.OSX)) {
project.addFile('Sources/viewport_server.h');
project.addFile('Sources/viewport_server.cpp');
}
if (flags.with_networking && platform !== Platform.Windows) {
project.addLib('ssl');
project.addLib('crypto');
}
if (flags.with_worker) {
project.addDefine('WITH_WORKER');
project.addFile('Sources/worker.h');
@ -58,13 +126,26 @@ if (platform === Platform.Windows) {
project.addDefine('_HAS_ITERATOR_DEBUGGING=0');
project.addDefine('_ITERATOR_DEBUG_LEVEL=0');
}
if (flags.with_networking) {
project.addLib('ws2_32');
project.addLib('crypt32');
project.addLib('secur32');
}
}
else if (platform === Platform.Linux) {
project.addLib('v8_monolith -L' + libdir);
project.addDefine("KINC_NO_WAYLAND");
if (flags.with_networking) {
project.addLib('pthread');
}
}
else if (platform === Platform.OSX) {
project.addLib('v8/libraries/macos/release/libv8_monolith.a');
if (flags.with_networking) {
project.addLib('-framework Foundation');
project.addLib('-framework Security');
project.addLib('-framework SystemConfiguration');
}
}
project.flatten();