Merge pull request 'Here comes the RunT!' (#1) from Onek8/LNXCORE:main into main
Reviewed-on: LeenkxTeam/LNXCORE#1
This commit is contained in:
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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>
|
||||
|
||||
17
README.md
17
README.md
@ -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
9
Sources/async_engine.cpp
Normal 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
199
Sources/async_engine.h
Normal 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
75
Sources/broadcast_queue.h
Normal 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
181
Sources/connection_pool.cpp
Normal 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
48
Sources/connection_pool.h
Normal 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;
|
||||
4
Sources/global_thread_pool.cpp
Normal file
4
Sources/global_thread_pool.cpp
Normal file
@ -0,0 +1,4 @@
|
||||
#include "global_thread_pool.h"
|
||||
|
||||
std::unique_ptr<ThreadPool> GlobalThreadPool::instance_;
|
||||
std::once_flag GlobalThreadPool::initialized_;
|
||||
25
Sources/global_thread_pool.h
Normal file
25
Sources/global_thread_pool.h
Normal 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
2387
Sources/httprequest.cpp
Normal file
File diff suppressed because it is too large
Load Diff
235
Sources/httprequest.h
Normal file
235
Sources/httprequest.h
Normal 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
75
Sources/lockfree_queue.h
Normal 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);
|
||||
}
|
||||
};
|
||||
1350
Sources/main.cpp
1350
Sources/main.cpp
File diff suppressed because it is too large
Load Diff
259
Sources/ring_buffer.h
Normal file
259
Sources/ring_buffer.h
Normal 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
947
Sources/socket_bridge.cpp
Normal 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
25
Sources/socket_bridge.h
Normal 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
|
||||
86
Sources/socket_optimization.h
Normal file
86
Sources/socket_optimization.h
Normal 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;
|
||||
}
|
||||
};
|
||||
311
Sources/socket_v8_bindings.cpp
Normal file
311
Sources/socket_v8_bindings.cpp
Normal 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
|
||||
|
||||
|
||||
}
|
||||
20
Sources/socket_v8_bindings.h
Normal file
20
Sources/socket_v8_bindings.h
Normal 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
79
Sources/thread_pool.h
Normal 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
580
Sources/viewport_server.cpp
Normal 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
122
Sources/viewport_server.h
Normal 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
2865
Sources/websocket.cpp
Normal file
File diff suppressed because it is too large
Load Diff
199
Sources/websocket.h
Normal file
199
Sources/websocket.h
Normal 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
1249
Sources/websocket_bridge.cpp
Normal file
File diff suppressed because it is too large
Load Diff
160
Sources/websocket_bridge.h
Normal file
160
Sources/websocket_bridge.h
Normal 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);
|
||||
}
|
||||
43
Sources/websocket_config.h
Normal file
43
Sources/websocket_config.h
Normal 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
|
||||
230
Sources/websocket_v8_bindings.cpp
Normal file
230
Sources/websocket_v8_bindings.cpp
Normal 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
|
||||
}
|
||||
12
Sources/websocket_v8_bindings.h
Normal file
12
Sources/websocket_v8_bindings.h
Normal 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);
|
||||
@ -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;
|
||||
}
|
||||
|
||||
87
kfile.js
87
kfile.js
@ -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();
|
||||
|
||||
Reference in New Issue
Block a user