Requests working good.
This commit is contained in:
commit
9f27915cab
12
Makefile
Normal file
12
Makefile
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
make:
|
||||
gcc main.c wren.c -lcurl -lm -lpthread -o wren
|
||||
|
||||
|
||||
make3:
|
||||
g++ main3.c wren.c -lcurl -lm -o wren3
|
||||
|
||||
|
||||
|
||||
run:
|
||||
./wren_requests_example
|
79
async_http.c
Normal file
79
async_http.c
Normal file
@ -0,0 +1,79 @@
|
||||
#include "httplib.h"
|
||||
#include "wren.h"
|
||||
|
||||
// A struct to hold the context for an asynchronous HTTP request
|
||||
struct RequestContext {
|
||||
std::string url;
|
||||
WrenHandle* callback;
|
||||
WrenVM* vm;
|
||||
std::string response;
|
||||
bool error;
|
||||
};
|
||||
|
||||
// A class to manage asynchronous HTTP requests
|
||||
class AsyncHttp {
|
||||
public:
|
||||
AsyncHttp(WrenVM* vm) : vm_(vm), running_(true) {
|
||||
// Create a pool of worker threads
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
threads_.emplace_back([this] {
|
||||
while (running_) {
|
||||
RequestContext* context = requestQueue_.pop();
|
||||
if (!running_) break;
|
||||
|
||||
httplib::Client cli("http://example.com");
|
||||
if (auto res = cli.Get(context->url.c_str())) {
|
||||
context->response = res->body;
|
||||
context->error = false;
|
||||
} else {
|
||||
context->response = "Error: " + to_string(res.error());
|
||||
context->error = true;
|
||||
}
|
||||
|
||||
completionQueue_.push(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
~AsyncHttp() {
|
||||
running_ = false;
|
||||
// Add dummy requests to unblock worker threads
|
||||
for (size_t i = 0; i < threads_.size(); ++i) {
|
||||
requestQueue_.push(nullptr);
|
||||
}
|
||||
for (auto& thread : threads_) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void request(const std::string& url, WrenHandle* callback) {
|
||||
RequestContext* context = new RequestContext{url, callback, vm_};
|
||||
requestQueue_.push(context);
|
||||
}
|
||||
|
||||
void processCompletions() {
|
||||
while (!completionQueue_.empty()) {
|
||||
RequestContext* context = completionQueue_.pop();
|
||||
|
||||
// Create a handle for the callback function
|
||||
WrenHandle* callHandle = wrenMakeCallHandle(vm_, "call(_)");
|
||||
|
||||
wrenEnsureSlots(vm_, 2);
|
||||
wrenSetSlotHandle(vm_, 0, context->callback);
|
||||
wrenSetSlotString(vm_, 1, context->response.c_str());
|
||||
wrenCall(vm_, callHandle);
|
||||
|
||||
wrenReleaseHandle(vm_, callHandle);
|
||||
wrenReleaseHandle(vm_, context->callback);
|
||||
delete context;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
WrenVM* vm_;
|
||||
bool running_;
|
||||
std::vector<std::thread> threads_;
|
||||
ThreadSafeQueue<RequestContext*> requestQueue_;
|
||||
ThreadSafeQueue<RequestContext*> completionQueue_;
|
||||
};
|
135
backend.cpp
Normal file
135
backend.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
// backend.cpp (Corrected)
|
||||
#include "httplib.h"
|
||||
#include "wren.h"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
// A struct to hold the response data for our foreign object
|
||||
struct ResponseData {
|
||||
bool isError;
|
||||
int statusCode;
|
||||
std::string body;
|
||||
};
|
||||
|
||||
// --- Response Class Foreign Methods ---
|
||||
|
||||
void responseAllocate(WrenVM* vm) {
|
||||
// This is the constructor for the Response class.
|
||||
ResponseData* data = (ResponseData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(ResponseData));
|
||||
data->isError = false;
|
||||
data->statusCode = 0;
|
||||
}
|
||||
|
||||
void responseIsError(WrenVM* vm) {
|
||||
ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotBool(vm, 0, data->isError);
|
||||
}
|
||||
|
||||
void responseStatusCode(WrenVM* vm) {
|
||||
ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotDouble(vm, 0, data->statusCode);
|
||||
}
|
||||
|
||||
void responseBody(WrenVM* vm) {
|
||||
ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotBytes(vm, 0, data->body.c_str(), data->body.length());
|
||||
}
|
||||
|
||||
void responseJson(WrenVM* vm) {
|
||||
// For a real implementation, you would use a JSON library here.
|
||||
// For this example, we just return the body text.
|
||||
ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotBytes(vm, 0, data->body.c_str(), data->body.length());
|
||||
}
|
||||
|
||||
// --- Requests Class Foreign Methods ---
|
||||
|
||||
void requestsGet(WrenVM* vm) {
|
||||
const char* url = wrenGetSlotString(vm, 1);
|
||||
// TODO: Handle headers from slot 2.
|
||||
|
||||
httplib::Client cli("jsonplaceholder.typicode.com");
|
||||
auto res = cli.Get("/posts/1");
|
||||
|
||||
// CHANGED: We need two slots: one for the Response class, one for the new instance.
|
||||
wrenEnsureSlots(vm, 2);
|
||||
|
||||
// CHANGED: Get the 'Response' class from the 'requests' module and put it in slot 1.
|
||||
wrenGetVariable(vm, "requests", "Response", 1);
|
||||
|
||||
// CHANGED: Create a new foreign object instance of the class in slot 1.
|
||||
// The new instance is placed in slot 0, which becomes the return value.
|
||||
ResponseData* data = (ResponseData*)wrenSetSlotNewForeign(vm, 0, 1, sizeof(ResponseData));
|
||||
|
||||
if (res) {
|
||||
data->isError = false;
|
||||
data->statusCode = res->status;
|
||||
data->body = res->body;
|
||||
} else {
|
||||
data->isError = true;
|
||||
data->statusCode = -1;
|
||||
data->body = "GET request failed.";
|
||||
}
|
||||
}
|
||||
|
||||
void requestsPost(WrenVM* vm) {
|
||||
const char* url = wrenGetSlotString(vm, 1);
|
||||
const char* body = wrenGetSlotString(vm, 2);
|
||||
const char* contentType = wrenGetSlotString(vm, 3);
|
||||
// TODO: Handle headers from slot 4.
|
||||
|
||||
httplib::Client cli("jsonplaceholder.typicode.com");
|
||||
auto res = cli.Post("/posts", body, contentType);
|
||||
|
||||
// CHANGED: We need two slots: one for the Response class, one for the new instance.
|
||||
wrenEnsureSlots(vm, 2);
|
||||
|
||||
// CHANGED: Get the 'Response' class from the 'requests' module and put it in slot 1.
|
||||
wrenGetVariable(vm, "requests", "Response", 1);
|
||||
|
||||
// CHANGED: Create a new foreign object instance of the class in slot 1.
|
||||
// The new instance is placed in slot 0, which becomes the return value.
|
||||
ResponseData* data = (ResponseData*)wrenSetSlotNewForeign(vm, 0, 1, sizeof(ResponseData));
|
||||
|
||||
if (res) {
|
||||
data->isError = false;
|
||||
data->statusCode = res->status;
|
||||
data->body = res->body;
|
||||
} else {
|
||||
data->isError = true;
|
||||
data->statusCode = -1;
|
||||
data->body = "POST request failed.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- FFI Binding Functions ---
|
||||
|
||||
WrenForeignMethodFn bindForeignMethod(WrenVM* vm, const char* module,
|
||||
const char* className, bool isStatic, const char* signature) {
|
||||
if (strcmp(module, "requests") != 0) return NULL;
|
||||
|
||||
if (strcmp(className, "Requests") == 0 && isStatic) {
|
||||
if (strcmp(signature, "get_(_,_)") == 0) return requestsGet;
|
||||
if (strcmp(signature, "post_(_,_,_,_)") == 0) return requestsPost;
|
||||
}
|
||||
|
||||
if (strcmp(className, "Response") == 0 && !isStatic) {
|
||||
if (strcmp(signature, "isError") == 0) return responseIsError;
|
||||
if (strcmp(signature, "statusCode") == 0) return responseStatusCode;
|
||||
if (strcmp(signature, "body") == 0) return responseBody;
|
||||
if (strcmp(signature, "json()") == 0) return responseJson;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WrenForeignClassMethods bindForeignClass(WrenVM* vm, const char* module, const char* className) {
|
||||
WrenForeignClassMethods methods = {0};
|
||||
if (strcmp(module, "requests") == 0) {
|
||||
if (strcmp(className, "Response") == 0) {
|
||||
methods.allocate = responseAllocate;
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
206
main.c
Normal file
206
main.c
Normal file
@ -0,0 +1,206 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <curl/curl.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "wren.h"
|
||||
#include "requests_backend.c"
|
||||
#include "socket_backend.c"
|
||||
|
||||
// --- Global flag to control the main loop ---
|
||||
static volatile bool g_mainFiberIsDone = false;
|
||||
|
||||
// --- Foreign function for Wren to signal the host to exit ---
|
||||
void hostSignalDone(WrenVM* vm) {
|
||||
(void)vm;
|
||||
g_mainFiberIsDone = true;
|
||||
}
|
||||
|
||||
// --- File/VM Setup ---
|
||||
static char* readFile(const char* path) {
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (file == NULL) return NULL;
|
||||
fseek(file, 0L, SEEK_END);
|
||||
size_t fileSize = ftell(file);
|
||||
rewind(file);
|
||||
char* buffer = (char*)malloc(fileSize + 1);
|
||||
if (!buffer) { fclose(file); return NULL; }
|
||||
size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
|
||||
if (bytesRead < fileSize) {
|
||||
free(buffer);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
buffer[bytesRead] = '\0';
|
||||
fclose(file);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void writeFn(WrenVM* vm, const char* text) { (void)vm; printf("%s", text); }
|
||||
|
||||
static void errorFn(WrenVM* vm, WrenErrorType type, const char* module, int line, const char* message) {
|
||||
(void)vm;
|
||||
switch (type) {
|
||||
case WREN_ERROR_COMPILE:
|
||||
fprintf(stderr, "[%s line %d] [Error] %s\n", module, line, message);
|
||||
break;
|
||||
case WREN_ERROR_RUNTIME:
|
||||
fprintf(stderr, "[Runtime Error] %s\n", message);
|
||||
g_mainFiberIsDone = true; // Stop on runtime errors
|
||||
break;
|
||||
case WREN_ERROR_STACK_TRACE:
|
||||
fprintf(stderr, "[%s line %d] in %s\n", module, line, message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void onModuleComplete(WrenVM* vm, const char* name, WrenLoadModuleResult result) {
|
||||
(void)vm; (void)name;
|
||||
if (result.source) free((void*)result.source);
|
||||
}
|
||||
|
||||
static WrenLoadModuleResult loadModule(WrenVM* vm, const char* name) {
|
||||
(void)vm;
|
||||
WrenLoadModuleResult result = {0};
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "%s.wren", name);
|
||||
char* source = readFile(path);
|
||||
if (source != NULL) {
|
||||
result.source = source;
|
||||
result.onComplete = onModuleComplete;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- Combined Foreign Function Binders ---
|
||||
WrenForeignMethodFn combinedBindForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) {
|
||||
// Delegate to the socket backend's binder
|
||||
if (strcmp(module, "socket") == 0) {
|
||||
return bindSocketForeignMethod(vm, module, className, isStatic, signature);
|
||||
}
|
||||
|
||||
// Delegate to the requests backend's binder
|
||||
if (strcmp(module, "requests") == 0) {
|
||||
return bindForeignMethod(vm, module, className, isStatic, signature);
|
||||
}
|
||||
|
||||
// Handle host-specific methods
|
||||
if (strcmp(module, "main") == 0 && strcmp(className, "Host") == 0 && isStatic) {
|
||||
if (strcmp(signature, "signalDone()") == 0) return hostSignalDone;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WrenForeignClassMethods combinedBindForeignClass(WrenVM* vm, const char* module, const char* className) {
|
||||
// Delegate to the socket backend's class binder
|
||||
if (strcmp(module, "socket") == 0) {
|
||||
return bindSocketForeignClass(vm, module, className);
|
||||
}
|
||||
|
||||
// Delegate to the requests backend's class binder
|
||||
if (strcmp(module, "requests") == 0) {
|
||||
return bindForeignClass(vm, module, className);
|
||||
}
|
||||
|
||||
WrenForeignClassMethods methods = {0, 0};
|
||||
return methods;
|
||||
}
|
||||
|
||||
|
||||
// --- Main Application Entry Point ---
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <script.wren>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize libcurl for the requests module
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
|
||||
WrenConfiguration config;
|
||||
wrenInitConfiguration(&config);
|
||||
config.writeFn = writeFn;
|
||||
config.errorFn = errorFn;
|
||||
config.bindForeignMethodFn = combinedBindForeignMethod;
|
||||
config.bindForeignClassFn = combinedBindForeignClass;
|
||||
config.loadModuleFn = loadModule;
|
||||
|
||||
WrenVM* vm = wrenNewVM(&config);
|
||||
|
||||
// ** Initialize BOTH managers **
|
||||
socketManager_create(vm);
|
||||
httpManager_create(vm);
|
||||
|
||||
char* mainSource = readFile(argv[1]);
|
||||
if (!mainSource) {
|
||||
fprintf(stderr, "Could not open script: %s\n", argv[1]);
|
||||
socketManager_destroy();
|
||||
httpManager_destroy();
|
||||
wrenFreeVM(vm);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
wrenInterpret(vm, "main", mainSource);
|
||||
free(mainSource);
|
||||
|
||||
if (g_mainFiberIsDone) {
|
||||
socketManager_destroy();
|
||||
httpManager_destroy();
|
||||
wrenFreeVM(vm);
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenGetVariable(vm, "main", "mainFiber", 0);
|
||||
WrenHandle* mainFiberHandle = wrenGetSlotHandle(vm, 0);
|
||||
WrenHandle* callHandle = wrenMakeCallHandle(vm, "call()");
|
||||
|
||||
// === Main Event Loop ===
|
||||
while (!g_mainFiberIsDone) {
|
||||
// ** Process completions for BOTH managers **
|
||||
socketManager_processCompletions();
|
||||
httpManager_processCompletions();
|
||||
|
||||
// Resume the main Wren fiber
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenSetSlotHandle(vm, 0, mainFiberHandle);
|
||||
WrenInterpretResult result = wrenCall(vm, callHandle);
|
||||
if (result == WREN_RESULT_RUNTIME_ERROR) {
|
||||
g_mainFiberIsDone = true;
|
||||
}
|
||||
|
||||
// Prevent 100% CPU usage
|
||||
#ifdef _WIN32
|
||||
Sleep(1);
|
||||
#else
|
||||
usleep(1000); // 1ms
|
||||
#endif
|
||||
}
|
||||
|
||||
// Process any final completions before shutting down
|
||||
socketManager_processCompletions();
|
||||
httpManager_processCompletions();
|
||||
|
||||
wrenReleaseHandle(vm, mainFiberHandle);
|
||||
wrenReleaseHandle(vm, callHandle);
|
||||
|
||||
// ** Destroy BOTH managers **
|
||||
socketManager_destroy();
|
||||
httpManager_destroy();
|
||||
|
||||
wrenFreeVM(vm);
|
||||
curl_global_cleanup();
|
||||
|
||||
printf("\nHost application finished.\n");
|
||||
return 0;
|
||||
}
|
27070
merged_source_files.txt
Normal file
27070
merged_source_files.txt
Normal file
File diff suppressed because it is too large
Load Diff
31
requests.wren
Normal file
31
requests.wren
Normal file
@ -0,0 +1,31 @@
|
||||
// requests.wren
|
||||
foreign class Response {
|
||||
construct new() {}
|
||||
|
||||
foreign isError
|
||||
foreign statusCode
|
||||
foreign body
|
||||
foreign json()
|
||||
}
|
||||
|
||||
class Requests {
|
||||
// Foreign methods now expect a callback function.
|
||||
foreign static get_(url, headers, callback)
|
||||
foreign static post_(url, body, contentType, headers, callback)
|
||||
|
||||
static get(url, headers, callback) {
|
||||
if (!(callback is Fn) || callback.arity != 2) {
|
||||
Fiber.abort("Callback must be a function that accepts 2 arguments: (error, response).")
|
||||
}
|
||||
get_(url, headers, callback)
|
||||
}
|
||||
|
||||
static post(url, body, headers, callback) {
|
||||
if (!(callback is Fn) || callback.arity != 2) {
|
||||
Fiber.abort("Callback must be a function that accepts 2 arguments: (error, response).")
|
||||
}
|
||||
var contentType = headers != null && headers.containsKey("Content-Type") ?
|
||||
headers["Content-Type"] : "application/json"
|
||||
post_(url, body, contentType, headers, callback)
|
||||
}
|
||||
}
|
385
requests_backend.c
Normal file
385
requests_backend.c
Normal file
@ -0,0 +1,385 @@
|
||||
#include "wren.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <curl/curl.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
typedef HANDLE thread_t;
|
||||
typedef CRITICAL_SECTION mutex_t;
|
||||
typedef CONDITION_VARIABLE cond_t;
|
||||
#else
|
||||
#include <pthread.h>
|
||||
typedef pthread_t thread_t;
|
||||
typedef pthread_mutex_t mutex_t;
|
||||
typedef pthread_cond_t cond_t;
|
||||
#endif
|
||||
|
||||
// --- Data Structures ---
|
||||
|
||||
typedef struct {
|
||||
int isError;
|
||||
long statusCode;
|
||||
char* body;
|
||||
size_t body_len;
|
||||
} ResponseData;
|
||||
|
||||
typedef struct {
|
||||
char* memory;
|
||||
size_t size;
|
||||
} MemoryStruct;
|
||||
|
||||
typedef struct HttpContext {
|
||||
WrenVM* vm;
|
||||
WrenHandle* callback;
|
||||
|
||||
char* url;
|
||||
char* method;
|
||||
char* body;
|
||||
struct curl_slist* headers;
|
||||
|
||||
bool success;
|
||||
char* response_body;
|
||||
size_t response_body_len;
|
||||
long status_code;
|
||||
char* error_message;
|
||||
struct HttpContext* next;
|
||||
} HttpContext;
|
||||
|
||||
|
||||
// --- Thread-Safe Queue ---
|
||||
|
||||
typedef struct {
|
||||
HttpContext *head, *tail;
|
||||
mutex_t mutex;
|
||||
cond_t cond;
|
||||
} ThreadSafeQueue;
|
||||
|
||||
void http_queue_init(ThreadSafeQueue* q) {
|
||||
q->head = q->tail = NULL;
|
||||
#ifdef _WIN32
|
||||
InitializeCriticalSection(&q->mutex);
|
||||
InitializeConditionVariable(&q->cond);
|
||||
#else
|
||||
pthread_mutex_init(&q->mutex, NULL);
|
||||
pthread_cond_init(&q->cond, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void http_queue_destroy(ThreadSafeQueue* q) {
|
||||
#ifdef _WIN32
|
||||
DeleteCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_mutex_destroy(&q->mutex);
|
||||
pthread_cond_destroy(&q->cond);
|
||||
#endif
|
||||
}
|
||||
|
||||
void http_queue_push(ThreadSafeQueue* q, HttpContext* context) {
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
#endif
|
||||
|
||||
if(context) context->next = NULL;
|
||||
if (q->tail) q->tail->next = context;
|
||||
else q->head = context;
|
||||
q->tail = context;
|
||||
|
||||
#ifdef _WIN32
|
||||
WakeConditionVariable(&q->cond);
|
||||
LeaveCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_cond_signal(&q->cond);
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
HttpContext* http_queue_pop(ThreadSafeQueue* q) {
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&q->mutex);
|
||||
while (q->head == NULL) {
|
||||
SleepConditionVariableCS(&q->cond, &q->mutex, INFINITE);
|
||||
}
|
||||
#else
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
while (q->head == NULL) {
|
||||
pthread_cond_wait(&q->cond, &q->mutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
HttpContext* context = q->head;
|
||||
q->head = q->head->next;
|
||||
if (q->head == NULL) q->tail = NULL;
|
||||
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
#endif
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
bool http_queue_empty(ThreadSafeQueue* q) {
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&q->mutex);
|
||||
bool empty = (q->head == NULL);
|
||||
LeaveCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
bool empty = (q->head == NULL);
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
#endif
|
||||
return empty;
|
||||
}
|
||||
|
||||
|
||||
// --- libcurl Helpers ---
|
||||
static size_t write_memory_callback(void *contents, size_t size, size_t nmemb, void *userp) {
|
||||
size_t realsize = size * nmemb;
|
||||
MemoryStruct *mem = (MemoryStruct *)userp;
|
||||
char *ptr = (char*)realloc(mem->memory, mem->size + realsize + 1);
|
||||
if (ptr == NULL) return 0;
|
||||
mem->memory = ptr;
|
||||
memcpy(&(mem->memory[mem->size]), contents, realsize);
|
||||
mem->size += realsize;
|
||||
mem->memory[mem->size] = 0;
|
||||
return realsize;
|
||||
}
|
||||
|
||||
// --- Async HTTP Manager ---
|
||||
|
||||
typedef struct {
|
||||
WrenVM* vm;
|
||||
volatile bool running;
|
||||
thread_t threads[4];
|
||||
ThreadSafeQueue requestQueue;
|
||||
ThreadSafeQueue completionQueue;
|
||||
} AsyncHttpManager;
|
||||
|
||||
static AsyncHttpManager* httpManager = NULL;
|
||||
|
||||
void free_http_context(HttpContext* context) {
|
||||
if (!context) return;
|
||||
free(context->url);
|
||||
free(context->method);
|
||||
free(context->body);
|
||||
curl_slist_free_all(context->headers);
|
||||
free(context->response_body);
|
||||
free(context->error_message);
|
||||
free(context);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD WINAPI httpWorkerThread(LPVOID arg) {
|
||||
#else
|
||||
void* httpWorkerThread(void* arg) {
|
||||
#endif
|
||||
AsyncHttpManager* manager = (AsyncHttpManager*)arg;
|
||||
while (manager->running) {
|
||||
HttpContext* context = http_queue_pop(&manager->requestQueue);
|
||||
if (!context || !manager->running) {
|
||||
if (context) free_http_context(context);
|
||||
break;
|
||||
}
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
if (curl) {
|
||||
MemoryStruct chunk;
|
||||
chunk.memory = (char*)malloc(1);
|
||||
chunk.size = 0;
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, context->url);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_memory_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "wren-curl-agent/1.0");
|
||||
|
||||
if (strcmp(context->method, "POST") == 0) {
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, context->body);
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, context->headers);
|
||||
}
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
if (res == CURLE_OK) {
|
||||
context->success = true;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &context->status_code);
|
||||
context->response_body = chunk.memory;
|
||||
context->response_body_len = chunk.size;
|
||||
} else {
|
||||
context->success = false;
|
||||
context->status_code = -1;
|
||||
context->error_message = strdup(curl_easy_strerror(res));
|
||||
free(chunk.memory);
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
} else {
|
||||
context->success = false;
|
||||
context->error_message = strdup("Failed to initialize cURL handle.");
|
||||
}
|
||||
http_queue_push(&manager->completionQueue, context);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void httpManager_create(WrenVM* vm) {
|
||||
httpManager = (AsyncHttpManager*)malloc(sizeof(AsyncHttpManager));
|
||||
httpManager->vm = vm;
|
||||
httpManager->running = true;
|
||||
http_queue_init(&httpManager->requestQueue);
|
||||
http_queue_init(&httpManager->completionQueue);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
#ifdef _WIN32
|
||||
httpManager->threads[i] = CreateThread(NULL, 0, httpWorkerThread, httpManager, 0, NULL);
|
||||
#else
|
||||
pthread_create(&httpManager->threads[i], NULL, httpWorkerThread, httpManager);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void httpManager_destroy() {
|
||||
httpManager->running = false;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
http_queue_push(&httpManager->requestQueue, NULL);
|
||||
}
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
#ifdef _WIN32
|
||||
WaitForSingleObject(httpManager->threads[i], INFINITE);
|
||||
CloseHandle(httpManager->threads[i]);
|
||||
#else
|
||||
pthread_join(httpManager->threads[i], NULL);
|
||||
#endif
|
||||
}
|
||||
http_queue_destroy(&httpManager->requestQueue);
|
||||
http_queue_destroy(&httpManager->completionQueue);
|
||||
free(httpManager);
|
||||
}
|
||||
|
||||
void httpManager_processCompletions() {
|
||||
while (!http_queue_empty(&httpManager->completionQueue)) {
|
||||
HttpContext* context = http_queue_pop(&httpManager->completionQueue);
|
||||
|
||||
WrenHandle* callHandle = wrenMakeCallHandle(httpManager->vm, "call(_,_)");
|
||||
wrenEnsureSlots(httpManager->vm, 3);
|
||||
wrenSetSlotHandle(httpManager->vm, 0, context->callback);
|
||||
|
||||
if (context->success) {
|
||||
wrenSetSlotNull(httpManager->vm, 1);
|
||||
|
||||
wrenGetVariable(httpManager->vm, "requests", "Response", 2);
|
||||
void* foreign = wrenSetSlotNewForeign(httpManager->vm, 2, 2, sizeof(ResponseData));
|
||||
ResponseData* data = (ResponseData*)foreign;
|
||||
data->isError = false;
|
||||
data->statusCode = context->status_code;
|
||||
data->body = context->response_body;
|
||||
data->body_len = context->response_body_len;
|
||||
context->response_body = NULL;
|
||||
} else {
|
||||
wrenSetSlotString(httpManager->vm, 1, context->error_message);
|
||||
wrenSetSlotNull(httpManager->vm, 2);
|
||||
}
|
||||
|
||||
wrenCall(httpManager->vm, callHandle);
|
||||
wrenReleaseHandle(httpManager->vm, context->callback);
|
||||
wrenReleaseHandle(httpManager->vm, callHandle);
|
||||
free_http_context(context);
|
||||
}
|
||||
}
|
||||
|
||||
void httpManager_submit(HttpContext* context) {
|
||||
http_queue_push(&httpManager->requestQueue, context);
|
||||
}
|
||||
|
||||
// --- Wren Foreign Methods ---
|
||||
|
||||
void responseFinalize(void* data) {
|
||||
ResponseData* response = (ResponseData*)data;
|
||||
free(response->body);
|
||||
}
|
||||
|
||||
void responseAllocate(WrenVM* vm) {
|
||||
ResponseData* data = (ResponseData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(ResponseData));
|
||||
data->isError = 0;
|
||||
data->statusCode = 0;
|
||||
data->body = NULL;
|
||||
data->body_len = 0;
|
||||
}
|
||||
|
||||
void responseIsError(WrenVM* vm) {
|
||||
ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotBool(vm, 0, data->isError ? true : false);
|
||||
}
|
||||
|
||||
void responseStatusCode(WrenVM* vm) {
|
||||
ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotDouble(vm, 0, (double)data->statusCode);
|
||||
}
|
||||
|
||||
void responseBody(WrenVM* vm) {
|
||||
ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotBytes(vm, 0, data->body ? data->body : "", data->body_len);
|
||||
}
|
||||
|
||||
void responseJson(WrenVM* vm) {
|
||||
// CORRECTED: Replaced incorrect call with the actual logic.
|
||||
ResponseData* data = (ResponseData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotBytes(vm, 0, data->body ? data->body : "", data->body_len);
|
||||
}
|
||||
|
||||
void requestsGet(WrenVM* vm) {
|
||||
HttpContext* context = (HttpContext*)calloc(1, sizeof(HttpContext));
|
||||
context->vm = vm;
|
||||
context->method = strdup("GET");
|
||||
context->url = strdup(wrenGetSlotString(vm, 1));
|
||||
context->callback = wrenGetSlotHandle(vm, 3);
|
||||
httpManager_submit(context);
|
||||
}
|
||||
|
||||
void requestsPost(WrenVM* vm) {
|
||||
HttpContext* context = (HttpContext*)calloc(1, sizeof(HttpContext));
|
||||
context->vm = vm;
|
||||
context->method = strdup("POST");
|
||||
context->url = strdup(wrenGetSlotString(vm, 1));
|
||||
context->body = strdup(wrenGetSlotString(vm, 2));
|
||||
const char* contentType = wrenGetSlotString(vm, 3);
|
||||
char contentTypeHeader[256];
|
||||
snprintf(contentTypeHeader, sizeof(contentTypeHeader), "Content-Type: %s", contentType);
|
||||
context->headers = curl_slist_append(NULL, contentTypeHeader);
|
||||
context->callback = wrenGetSlotHandle(vm, 5);
|
||||
httpManager_submit(context);
|
||||
}
|
||||
|
||||
// --- FFI Binding Functions ---
|
||||
|
||||
WrenForeignMethodFn bindForeignMethod(WrenVM* vm, const char* module,
|
||||
const char* className, bool isStatic, const char* signature) {
|
||||
if (strcmp(module, "requests") != 0) return NULL;
|
||||
|
||||
if (strcmp(className, "Requests") == 0 && isStatic) {
|
||||
if (strcmp(signature, "get_(_,_,_)") == 0) return requestsGet;
|
||||
if (strcmp(signature, "post_(_,_,_,_,_)") == 0) return requestsPost;
|
||||
}
|
||||
|
||||
if (strcmp(className, "Response") == 0 && !isStatic) {
|
||||
if (strcmp(signature, "isError") == 0) return responseIsError;
|
||||
if (strcmp(signature, "statusCode") == 0) return responseStatusCode;
|
||||
if (strcmp(signature, "body") == 0) return responseBody;
|
||||
if (strcmp(signature, "json()") == 0) return responseJson;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WrenForeignClassMethods bindForeignClass(WrenVM* vm, const char* module, const char* className) {
|
||||
WrenForeignClassMethods methods = {0, 0};
|
||||
if (strcmp(module, "requests") == 0) {
|
||||
if (strcmp(className, "Response") == 0) {
|
||||
methods.allocate = responseAllocate;
|
||||
methods.finalize = responseFinalize;
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
59
requests_example.wren
Normal file
59
requests_example.wren
Normal file
@ -0,0 +1,59 @@
|
||||
// main.wren (Corrected)
|
||||
import "requests" for Requests
|
||||
|
||||
// This class provides a hook back into the C host
|
||||
foreign class Host {
|
||||
foreign static signalDone()
|
||||
}
|
||||
|
||||
var mainFiber = Fiber.new {
|
||||
System.print("--- Running 20 CONCURRENT GET requests ---")
|
||||
|
||||
var completed = 0
|
||||
|
||||
for (i in 1..1000) {
|
||||
Requests.get("https://jsonplaceholder.typicode.com/posts/%(i)", null) { |err, res|
|
||||
if (err) {
|
||||
System.print("Request #%(i) [GET] Error: %(err)")
|
||||
} else {
|
||||
System.print("Request #%(i) [GET] Status: %(res.statusCode)")
|
||||
}
|
||||
// CORRECTED: Create a new fiber for each atomic operation
|
||||
Fiber.new { completed = completed + 1 }.call()
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for GET requests to finish
|
||||
while (completed < 1000) {
|
||||
Fiber.yield()
|
||||
}
|
||||
|
||||
System.print("\n--- Running 20 CONCURRENT POST requests ---")
|
||||
|
||||
var postBody = """
|
||||
{ "title": "wren-test" }
|
||||
"""
|
||||
var headers = { "Content-Type": "application/json; charset=UTF-8" }
|
||||
var postCompleted = 0
|
||||
for (i in 1..1000) {
|
||||
Requests.post("https://jsonplaceholder.typicode.com/posts", postBody, headers) { |err, res|
|
||||
if (err) {
|
||||
System.print("Request #%(i) [POST] Error: %(err)")
|
||||
} else {
|
||||
System.print("Request #%(i) [POST] Response Code: %(res.statusCode)")
|
||||
}
|
||||
// CORRECTED: Create a new fiber for each atomic operation
|
||||
Fiber.new { postCompleted = postCompleted + 1 }.call()
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for POST requests to finish
|
||||
while (postCompleted < 1000) {
|
||||
Fiber.yield()
|
||||
}
|
||||
|
||||
System.print("\n--- All concurrent requests finished. ---")
|
||||
|
||||
// Tell the C host that we are done.
|
||||
Host.signalDone()
|
||||
}
|
49
socket.wren
Normal file
49
socket.wren
Normal file
@ -0,0 +1,49 @@
|
||||
// socket.wren (Corrected)
|
||||
foreign class Socket {
|
||||
// CORRECTED: Changed 'new_' to 'new' to match the standard convention.
|
||||
construct new() {}
|
||||
|
||||
foreign connect(host, port, callback)
|
||||
foreign listen(host, port, backlog)
|
||||
foreign accept(callback)
|
||||
foreign read(bytes)
|
||||
foreign close()
|
||||
|
||||
foreign isOpen
|
||||
foreign remoteAddress
|
||||
foreign remotePort
|
||||
|
||||
// Implemented in Wren
|
||||
write(data, callback) {
|
||||
write_(data, callback)
|
||||
}
|
||||
|
||||
readUntil(delimiter, callback) {
|
||||
var buffer = ""
|
||||
var readChunk
|
||||
readChunk = Fn.new {
|
||||
this.read(4096) { |err, data|
|
||||
if (err) {
|
||||
callback.call(err, null)
|
||||
return
|
||||
}
|
||||
|
||||
buffer = buffer + data
|
||||
var index = buffer.indexOf(delimiter)
|
||||
|
||||
if (index != -1) {
|
||||
var result = buffer.substring(0, index + delimiter.count)
|
||||
callback.call(null, result)
|
||||
} else {
|
||||
// Delimiter not found, read more data.
|
||||
readChunk.call()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Start reading.
|
||||
readChunk.call()
|
||||
}
|
||||
|
||||
// Private foreign method for writing
|
||||
foreign write_(data, callback)
|
||||
}
|
596
socket_backend.c
Normal file
596
socket_backend.c
Normal file
@ -0,0 +1,596 @@
|
||||
#include "wren.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
|
||||
// Platform-specific includes and definitions
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
typedef SOCKET socket_t;
|
||||
typedef int socklen_t;
|
||||
typedef HANDLE thread_t;
|
||||
typedef CRITICAL_SECTION mutex_t;
|
||||
typedef CONDITION_VARIABLE cond_t;
|
||||
#define IS_SOCKET_VALID(s) ((s) != INVALID_SOCKET)
|
||||
#define CLOSE_SOCKET(s) closesocket(s)
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/select.h>
|
||||
typedef int socket_t;
|
||||
typedef pthread_t thread_t;
|
||||
typedef pthread_mutex_t mutex_t;
|
||||
typedef pthread_cond_t cond_t;
|
||||
#define INVALID_SOCKET -1
|
||||
#define IS_SOCKET_VALID(s) ((s) >= 0)
|
||||
#define CLOSE_SOCKET(s) close(s)
|
||||
#endif
|
||||
|
||||
// --- Forward Declarations ---
|
||||
typedef struct SocketContext SocketContext;
|
||||
|
||||
// --- Socket Data Structures ---
|
||||
|
||||
typedef enum {
|
||||
SOCKET_OP_CONNECT,
|
||||
SOCKET_OP_ACCEPT,
|
||||
SOCKET_OP_READ,
|
||||
SOCKET_OP_WRITE,
|
||||
} SocketOp;
|
||||
|
||||
typedef struct {
|
||||
socket_t sock;
|
||||
bool isListener;
|
||||
} SocketData;
|
||||
|
||||
struct SocketContext {
|
||||
SocketOp operation;
|
||||
WrenVM* vm;
|
||||
WrenHandle* socketHandle;
|
||||
WrenHandle* callback;
|
||||
|
||||
// For connect
|
||||
char* host;
|
||||
int port;
|
||||
|
||||
// For write
|
||||
char* data;
|
||||
size_t dataLength;
|
||||
|
||||
// Results
|
||||
bool success;
|
||||
char* resultData;
|
||||
size_t resultDataLength;
|
||||
char* errorMessage;
|
||||
socket_t newSocket; // For accept
|
||||
struct SocketContext* next;
|
||||
};
|
||||
|
||||
// --- Thread-Safe Queue Implementation ---
|
||||
typedef struct {
|
||||
SocketContext *head, *tail;
|
||||
mutex_t mutex;
|
||||
cond_t cond;
|
||||
} ThreadSafeQueueSocket;
|
||||
|
||||
void queue_init(ThreadSafeQueueSocket* q) {
|
||||
q->head = q->tail = NULL;
|
||||
#ifdef _WIN32
|
||||
InitializeCriticalSection(&q->mutex);
|
||||
InitializeConditionVariable(&q->cond);
|
||||
#else
|
||||
pthread_mutex_init(&q->mutex, NULL);
|
||||
pthread_cond_init(&q->cond, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void queue_destroy(ThreadSafeQueueSocket* q) {
|
||||
#ifdef _WIN32
|
||||
DeleteCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_mutex_destroy(&q->mutex);
|
||||
pthread_cond_destroy(&q->cond);
|
||||
#endif
|
||||
}
|
||||
|
||||
void queue_push(ThreadSafeQueueSocket* q, SocketContext* context) {
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
#endif
|
||||
|
||||
if (context) {
|
||||
context->next = NULL;
|
||||
}
|
||||
|
||||
if (q->tail) {
|
||||
q->tail->next = context;
|
||||
} else {
|
||||
q->head = context;
|
||||
}
|
||||
q->tail = context;
|
||||
|
||||
#ifdef _WIN32
|
||||
WakeConditionVariable(&q->cond);
|
||||
LeaveCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_cond_signal(&q->cond);
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
SocketContext* queue_pop(ThreadSafeQueueSocket* q) {
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&q->mutex);
|
||||
while (q->head == NULL) {
|
||||
SleepConditionVariableCS(&q->cond, &q->mutex, INFINITE);
|
||||
}
|
||||
#else
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
while (q->head == NULL) {
|
||||
pthread_cond_wait(&q->cond, &q->mutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
SocketContext* context = q->head;
|
||||
if (context) {
|
||||
q->head = q->head->next;
|
||||
if (q->head == NULL) {
|
||||
q->tail = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
#endif
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
bool queue_empty(ThreadSafeQueueSocket* q) {
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&q->mutex);
|
||||
bool empty = (q->head == NULL);
|
||||
LeaveCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
bool empty = (q->head == NULL);
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
#endif
|
||||
return empty;
|
||||
}
|
||||
|
||||
// --- Asynchronous Socket Manager ---
|
||||
|
||||
typedef struct {
|
||||
WrenVM* vm;
|
||||
volatile bool running;
|
||||
thread_t worker_threads[4];
|
||||
ThreadSafeQueueSocket requestQueue;
|
||||
ThreadSafeQueueSocket completionQueue;
|
||||
} AsyncSocketManager;
|
||||
|
||||
static AsyncSocketManager* socketManager = NULL;
|
||||
|
||||
void free_socket_context_data(SocketContext* context) {
|
||||
if (!context) return;
|
||||
free(context->host);
|
||||
free(context->data);
|
||||
free(context->resultData);
|
||||
free(context->errorMessage);
|
||||
free(context);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD WINAPI workerThread(LPVOID arg);
|
||||
#else
|
||||
void* workerThread(void* arg);
|
||||
#endif
|
||||
|
||||
// --- Worker Thread Implementation ---
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD WINAPI workerThread(LPVOID arg) {
|
||||
#else
|
||||
void* workerThread(void* arg) {
|
||||
#endif
|
||||
AsyncSocketManager* manager = (AsyncSocketManager*)arg;
|
||||
|
||||
while (manager->running) {
|
||||
SocketContext* context = queue_pop(&manager->requestQueue);
|
||||
if (!context || !manager->running) {
|
||||
if (context) free_socket_context_data(context);
|
||||
break;
|
||||
}
|
||||
|
||||
wrenEnsureSlots(context->vm, 1);
|
||||
wrenSetSlotHandle(context->vm, 0, context->socketHandle);
|
||||
SocketData* socketData = (wrenGetSlotType(context->vm, 0) == WREN_TYPE_FOREIGN)
|
||||
? (SocketData*)wrenGetSlotForeign(context->vm, 0)
|
||||
: NULL;
|
||||
|
||||
if (!socketData || !IS_SOCKET_VALID(socketData->sock)) {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Invalid or closed socket object.");
|
||||
queue_push(&manager->completionQueue, context);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (context->operation) {
|
||||
case SOCKET_OP_CONNECT: {
|
||||
struct addrinfo hints = {0}, *addrs;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
char port_str[6];
|
||||
snprintf(port_str, 6, "%d", context->port);
|
||||
if (getaddrinfo(context->host, port_str, &hints, &addrs) != 0) {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Host lookup failed.");
|
||||
break;
|
||||
}
|
||||
|
||||
socket_t sock = INVALID_SOCKET;
|
||||
for (struct addrinfo* addr = addrs; addr; addr = addr->ai_next) {
|
||||
sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
|
||||
if (!IS_SOCKET_VALID(sock)) continue;
|
||||
if (connect(sock, addr->ai_addr, (int)addr->ai_addrlen) == 0) break;
|
||||
CLOSE_SOCKET(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
freeaddrinfo(addrs);
|
||||
|
||||
if (IS_SOCKET_VALID(sock)) {
|
||||
socketData->sock = sock;
|
||||
socketData->isListener = false;
|
||||
context->success = true;
|
||||
} else {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Connection failed.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCKET_OP_ACCEPT: {
|
||||
if (!socketData->isListener) {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Cannot accept on a non-listening socket.");
|
||||
break;
|
||||
}
|
||||
|
||||
// This is a blocking call. The worker thread will wait here.
|
||||
context->newSocket = accept(socketData->sock, NULL, NULL);
|
||||
context->success = IS_SOCKET_VALID(context->newSocket);
|
||||
if (!context->success) {
|
||||
#ifdef _WIN32
|
||||
// TODO: A more descriptive error using FormatMessageA
|
||||
context->errorMessage = strdup("Accept failed.");
|
||||
#else
|
||||
context->errorMessage = strdup(strerror(errno));
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCKET_OP_READ: {
|
||||
if (socketData->isListener) {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Cannot read from a listening socket.");
|
||||
break;
|
||||
}
|
||||
|
||||
char buf[4096];
|
||||
// This is a blocking call.
|
||||
ssize_t len = recv(socketData->sock, buf, sizeof(buf), 0);
|
||||
if (len > 0) {
|
||||
context->resultData = (char*)malloc(len);
|
||||
memcpy(context->resultData, buf, len);
|
||||
context->resultDataLength = len;
|
||||
context->success = true;
|
||||
} else {
|
||||
context->success = false;
|
||||
if (len == 0) {
|
||||
context->errorMessage = strdup("Connection closed by peer.");
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
context->errorMessage = strdup("Read failed.");
|
||||
#else
|
||||
context->errorMessage = strdup(strerror(errno));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCKET_OP_WRITE: {
|
||||
if (socketData->isListener) {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Cannot write to a listening socket.");
|
||||
break;
|
||||
}
|
||||
ssize_t written = send(socketData->sock, context->data, context->dataLength, 0);
|
||||
context->success = (written == (ssize_t)context->dataLength);
|
||||
if(!context->success) context->errorMessage = strdup("Write failed.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
queue_push(&manager->completionQueue, context);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- Manager Lifecycle ---
|
||||
|
||||
void socketManager_create(WrenVM* vm) {
|
||||
if (socketManager != NULL) return;
|
||||
socketManager = (AsyncSocketManager*)malloc(sizeof(AsyncSocketManager));
|
||||
socketManager->vm = vm;
|
||||
socketManager->running = true;
|
||||
|
||||
queue_init(&socketManager->requestQueue);
|
||||
queue_init(&socketManager->completionQueue);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
#ifdef _WIN32
|
||||
socketManager->worker_threads[i] = CreateThread(NULL, 0, workerThread, socketManager, 0, NULL);
|
||||
#else
|
||||
pthread_create(&socketManager->worker_threads[i], NULL, workerThread, socketManager);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void socketManager_destroy() {
|
||||
if (!socketManager) return;
|
||||
socketManager->running = false;
|
||||
|
||||
// Unblock all worker threads
|
||||
for (int i = 0; i < 4; i++) {
|
||||
queue_push(&socketManager->requestQueue, NULL);
|
||||
}
|
||||
|
||||
// Wait for threads to finish
|
||||
for (int i = 0; i < 4; i++) {
|
||||
#ifdef _WIN32
|
||||
WaitForSingleObject(socketManager->worker_threads[i], INFINITE);
|
||||
CloseHandle(socketManager->worker_threads[i]);
|
||||
#else
|
||||
pthread_join(socketManager->worker_threads[i], NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Clean up any remaining contexts in queues
|
||||
while (!queue_empty(&socketManager->requestQueue)) {
|
||||
free_socket_context_data(queue_pop(&socketManager->requestQueue));
|
||||
}
|
||||
while (!queue_empty(&socketManager->completionQueue)) {
|
||||
free_socket_context_data(queue_pop(&socketManager->completionQueue));
|
||||
}
|
||||
|
||||
queue_destroy(&socketManager->requestQueue);
|
||||
queue_destroy(&socketManager->completionQueue);
|
||||
|
||||
free(socketManager);
|
||||
socketManager = NULL;
|
||||
}
|
||||
|
||||
void socketManager_processCompletions() {
|
||||
if (!socketManager || queue_empty(&socketManager->completionQueue)) return;
|
||||
|
||||
WrenHandle* callHandle = wrenMakeCallHandle(socketManager->vm, "call(_,_)");
|
||||
while (!queue_empty(&socketManager->completionQueue)) {
|
||||
SocketContext* context = queue_pop(&socketManager->completionQueue);
|
||||
|
||||
wrenEnsureSlots(socketManager->vm, 3);
|
||||
wrenSetSlotHandle(socketManager->vm, 0, context->callback);
|
||||
if (context->success) {
|
||||
wrenSetSlotNull(socketManager->vm, 1); // error slot
|
||||
if (IS_SOCKET_VALID(context->newSocket)) { // Accept succeeded
|
||||
wrenGetVariable(socketManager->vm, "socket", "Socket", 2);
|
||||
void* foreign = wrenSetSlotNewForeign(socketManager->vm, 2, 2, sizeof(SocketData));
|
||||
SocketData* clientData = (SocketData*)foreign;
|
||||
clientData->sock = context->newSocket;
|
||||
clientData->isListener = false;
|
||||
} else if (context->resultData) { // Read succeeded
|
||||
wrenSetSlotBytes(socketManager->vm, 2, context->resultData, context->resultDataLength);
|
||||
} else { // Other successes (connect, write)
|
||||
wrenSetSlotNull(socketManager->vm, 2);
|
||||
}
|
||||
} else {
|
||||
wrenSetSlotString(socketManager->vm, 1, context->errorMessage ? context->errorMessage : "Unknown error.");
|
||||
wrenSetSlotNull(socketManager->vm, 2);
|
||||
}
|
||||
|
||||
wrenCall(socketManager->vm, callHandle);
|
||||
|
||||
wrenReleaseHandle(socketManager->vm, context->socketHandle);
|
||||
wrenReleaseHandle(socketManager->vm, context->callback);
|
||||
free_socket_context_data(context);
|
||||
}
|
||||
wrenReleaseHandle(socketManager->vm, callHandle);
|
||||
}
|
||||
|
||||
// --- Wren Foreign Methods ---
|
||||
|
||||
void socketAllocate(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(SocketData));
|
||||
data->sock = INVALID_SOCKET;
|
||||
data->isListener = false;
|
||||
}
|
||||
|
||||
void socketConnect(WrenVM* vm) {
|
||||
SocketContext* context = (SocketContext*)calloc(1, sizeof(SocketContext));
|
||||
context->operation = SOCKET_OP_CONNECT;
|
||||
context->vm = vm;
|
||||
context->socketHandle = wrenGetSlotHandle(vm, 0);
|
||||
context->host = strdup(wrenGetSlotString(vm, 1));
|
||||
context->port = (int)wrenGetSlotDouble(vm, 2);
|
||||
context->callback = wrenGetSlotHandle(vm, 3);
|
||||
queue_push(&socketManager->requestQueue, context);
|
||||
}
|
||||
|
||||
void socketListen(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
const char* host = wrenGetSlotString(vm, 1);
|
||||
int port = (int)wrenGetSlotDouble(vm, 2);
|
||||
int backlog = (int)wrenGetSlotDouble(vm, 3);
|
||||
|
||||
struct addrinfo hints = {0}, *addrs;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
char port_str[6];
|
||||
snprintf(port_str, 6, "%d", port);
|
||||
if (getaddrinfo(host, port_str, &hints, &addrs) != 0) {
|
||||
wrenSetSlotBool(vm, 0, false);
|
||||
return;
|
||||
}
|
||||
|
||||
socket_t sock = INVALID_SOCKET;
|
||||
for (struct addrinfo* addr = addrs; addr; addr = addr->ai_next) {
|
||||
sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
|
||||
if (!IS_SOCKET_VALID(sock)) continue;
|
||||
|
||||
int yes = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof(yes));
|
||||
|
||||
if (bind(sock, addr->ai_addr, (int)addr->ai_addrlen) == 0) break;
|
||||
|
||||
CLOSE_SOCKET(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
freeaddrinfo(addrs);
|
||||
|
||||
if (IS_SOCKET_VALID(sock) && listen(sock, backlog) == 0) {
|
||||
data->sock = sock;
|
||||
data->isListener = true;
|
||||
wrenSetSlotBool(vm, 0, true);
|
||||
} else {
|
||||
if(IS_SOCKET_VALID(sock)) CLOSE_SOCKET(sock);
|
||||
wrenSetSlotBool(vm, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
void socketAccept(WrenVM* vm) {
|
||||
SocketContext* context = (SocketContext*)calloc(1, sizeof(SocketContext));
|
||||
context->operation = SOCKET_OP_ACCEPT;
|
||||
context->vm = vm;
|
||||
context->socketHandle = wrenGetSlotHandle(vm, 0);
|
||||
context->callback = wrenGetSlotHandle(vm, 1);
|
||||
queue_push(&socketManager->requestQueue, context);
|
||||
}
|
||||
|
||||
void socketRead(WrenVM* vm) {
|
||||
SocketContext* context = (SocketContext*)calloc(1, sizeof(SocketContext));
|
||||
context->operation = SOCKET_OP_READ;
|
||||
context->vm = vm;
|
||||
context->socketHandle = wrenGetSlotHandle(vm, 0);
|
||||
context->callback = wrenGetSlotHandle(vm, 1);
|
||||
queue_push(&socketManager->requestQueue, context);
|
||||
}
|
||||
|
||||
void socketWrite(WrenVM* vm) {
|
||||
SocketContext* context = (SocketContext*)calloc(1, sizeof(SocketContext));
|
||||
context->operation = SOCKET_OP_WRITE;
|
||||
context->vm = vm;
|
||||
context->socketHandle = wrenGetSlotHandle(vm, 0);
|
||||
int len;
|
||||
const char* bytes = wrenGetSlotBytes(vm, 1, &len);
|
||||
context->data = (char*)malloc(len);
|
||||
memcpy(context->data, bytes, len);
|
||||
context->dataLength = len;
|
||||
context->callback = wrenGetSlotHandle(vm, 2);
|
||||
queue_push(&socketManager->requestQueue, context);
|
||||
}
|
||||
|
||||
void socketClose(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
if (IS_SOCKET_VALID(data->sock)) {
|
||||
CLOSE_SOCKET(data->sock);
|
||||
data->sock = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
void socketIsOpen(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotBool(vm, 0, IS_SOCKET_VALID(data->sock));
|
||||
}
|
||||
|
||||
void socketRemoteAddress(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
if (!IS_SOCKET_VALID(data->sock) || data->isListener) {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
char ipstr[INET6_ADDRSTRLEN];
|
||||
|
||||
if (getpeername(data->sock, (struct sockaddr*)&addr, &len) == 0) {
|
||||
if (addr.ss_family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((struct sockaddr_in*)&addr)->sin_addr, ipstr, sizeof(ipstr));
|
||||
} else {
|
||||
inet_ntop(AF_INET6, &((struct sockaddr_in6*)&addr)->sin6_addr, ipstr, sizeof(ipstr));
|
||||
}
|
||||
wrenSetSlotString(vm, 0, ipstr);
|
||||
} else {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void socketRemotePort(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
if (!IS_SOCKET_VALID(data->sock) || data->isListener) {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
|
||||
if (getpeername(data->sock, (struct sockaddr*)&addr, &len) == 0) {
|
||||
int port = 0;
|
||||
if (addr.ss_family == AF_INET) {
|
||||
port = ntohs(((struct sockaddr_in*)&addr)->sin_port);
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
port = ntohs(((struct sockaddr_in6*)&addr)->sin6_port);
|
||||
}
|
||||
wrenSetSlotDouble(vm, 0, (double)port);
|
||||
} else {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
}
|
||||
|
||||
WrenForeignMethodFn bindSocketForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) {
|
||||
if (strcmp(module, "socket") != 0) return NULL;
|
||||
if (strcmp(className, "Socket") == 0 && !isStatic) {
|
||||
if (strcmp(signature, "connect(_,_,_)") == 0) return socketConnect;
|
||||
if (strcmp(signature, "listen(_,_,_)") == 0) return socketListen;
|
||||
if (strcmp(signature, "accept(_)") == 0) return socketAccept;
|
||||
if (strcmp(signature, "read(_)") == 0) return socketRead;
|
||||
if (strcmp(signature, "write_(_,_)") == 0) return socketWrite;
|
||||
if (strcmp(signature, "close()") == 0) return socketClose;
|
||||
if (strcmp(signature, "isOpen") == 0) return socketIsOpen;
|
||||
if (strcmp(signature, "remoteAddress") == 0) return socketRemoteAddress;
|
||||
if (strcmp(signature, "remotePort") == 0) return socketRemotePort;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WrenForeignClassMethods bindSocketForeignClass(WrenVM* vm, const char* module, const char* className) {
|
||||
WrenForeignClassMethods methods = {0, 0};
|
||||
if (strcmp(module, "socket") == 0 && strcmp(className, "Socket") == 0) {
|
||||
methods.allocate = socketAllocate;
|
||||
}
|
||||
return methods;
|
||||
}
|
384
socket_backend.cpp
Normal file
384
socket_backend.cpp
Normal file
@ -0,0 +1,384 @@
|
||||
// socket_backend.cpp (Native Sockets Implementation)
|
||||
#include "wren.h"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
typedef SOCKET socket_t;
|
||||
#define IS_SOCKET_VALID(s) ((s) != INVALID_SOCKET)
|
||||
#define CLOSE_SOCKET(s) closesocket(s)
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <cerrno>
|
||||
typedef int socket_t;
|
||||
#define INVALID_SOCKET -1
|
||||
#define IS_SOCKET_VALID(s) ((s) >= 0)
|
||||
#define CLOSE_SOCKET(s) close(s)
|
||||
#endif
|
||||
|
||||
|
||||
// --- Thread-Safe Queue for Asynchronous Operations ---
|
||||
|
||||
template <typename T>
|
||||
class ThreadSafeQueue {
|
||||
public:
|
||||
void push(T item) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
queue_.push(item);
|
||||
cond_.notify_one();
|
||||
}
|
||||
|
||||
T pop() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
cond_.wait(lock, [this] { return !queue_.empty(); });
|
||||
T item = queue_.front();
|
||||
queue_.pop();
|
||||
return item;
|
||||
}
|
||||
|
||||
bool empty() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return queue_.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
std::queue<T> queue_;
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cond_;
|
||||
};
|
||||
|
||||
// --- Context for Asynchronous Socket Operations ---
|
||||
|
||||
enum class SocketOp {
|
||||
CONNECT,
|
||||
ACCEPT,
|
||||
READ,
|
||||
WRITE
|
||||
};
|
||||
|
||||
struct SocketData {
|
||||
socket_t sock;
|
||||
bool isListener;
|
||||
};
|
||||
|
||||
struct SocketContext {
|
||||
SocketOp operation;
|
||||
WrenVM* vm;
|
||||
WrenHandle* socketHandle;
|
||||
WrenHandle* callback;
|
||||
|
||||
// Operation-specific data
|
||||
std::string host;
|
||||
int port;
|
||||
std::string data;
|
||||
int bytesToRead;
|
||||
|
||||
// Result data
|
||||
bool success;
|
||||
std::string resultData;
|
||||
std::string errorMessage;
|
||||
socket_t newSocket; // For accepted connections
|
||||
};
|
||||
|
||||
|
||||
// --- Asynchronous Socket Manager ---
|
||||
|
||||
class AsyncSocketManager {
|
||||
public:
|
||||
AsyncSocketManager(WrenVM* vm) : vm_(vm), running_(true) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
threads_.emplace_back([this] { workerThread(); });
|
||||
}
|
||||
}
|
||||
|
||||
~AsyncSocketManager() {
|
||||
running_ = false;
|
||||
for (size_t i = 0; i < threads_.size(); ++i) {
|
||||
requestQueue_.push(nullptr);
|
||||
}
|
||||
for (auto& thread : threads_) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void submit(SocketContext* context) {
|
||||
requestQueue_.push(context);
|
||||
}
|
||||
|
||||
bool completionQueueEmpty() {
|
||||
return completionQueue_.empty();
|
||||
}
|
||||
|
||||
void processCompletions() {
|
||||
while (!completionQueue_.empty()) {
|
||||
SocketContext* context = completionQueue_.pop();
|
||||
|
||||
WrenHandle* callHandle = wrenMakeCallHandle(vm_, "call(_,_)");
|
||||
wrenEnsureSlots(vm_, 3);
|
||||
wrenSetSlotHandle(vm_, 0, context->callback);
|
||||
|
||||
if (context->success) {
|
||||
wrenSetSlotNull(vm_, 1); // No error
|
||||
switch (context->operation) {
|
||||
case SocketOp::ACCEPT: {
|
||||
wrenGetVariable(vm_, "socket", "Socket", 2);
|
||||
void* foreign = wrenSetSlotNewForeign(vm_, 2, 2, sizeof(SocketData));
|
||||
SocketData* clientData = (SocketData*)foreign;
|
||||
clientData->sock = context->newSocket;
|
||||
clientData->isListener = false;
|
||||
break;
|
||||
}
|
||||
case SocketOp::READ:
|
||||
wrenSetSlotBytes(vm_, 2, context->resultData.c_str(), context->resultData.length());
|
||||
break;
|
||||
default:
|
||||
wrenSetSlotNull(vm_, 2);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
wrenSetSlotString(vm_, 1, context->errorMessage.c_str());
|
||||
wrenSetSlotNull(vm_, 2);
|
||||
}
|
||||
|
||||
wrenCall(vm_, callHandle);
|
||||
|
||||
wrenReleaseHandle(vm_, context->socketHandle);
|
||||
wrenReleaseHandle(vm_, context->callback);
|
||||
wrenReleaseHandle(vm_, callHandle);
|
||||
delete context;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void workerThread() {
|
||||
while (running_) {
|
||||
SocketContext* context = requestQueue_.pop();
|
||||
if (!context || !running_) break;
|
||||
|
||||
wrenEnsureSlots(context->vm, 1);
|
||||
wrenSetSlotHandle(context->vm, 0, context->socketHandle);
|
||||
SocketData* socketData = (wrenGetSlotType(context->vm, 0) == WREN_TYPE_FOREIGN)
|
||||
? (SocketData*)wrenGetSlotForeign(context->vm, 0)
|
||||
: nullptr;
|
||||
|
||||
if (!socketData) {
|
||||
context->success = false;
|
||||
context->errorMessage = "Invalid socket object.";
|
||||
completionQueue_.push(context);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (context->operation) {
|
||||
case SocketOp::CONNECT: {
|
||||
addrinfo hints = {}, *addrs;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
getaddrinfo(context->host.c_str(), std::to_string(context->port).c_str(), &hints, &addrs);
|
||||
|
||||
socket_t sock = INVALID_SOCKET;
|
||||
for (addrinfo* addr = addrs; addr; addr = addr->ai_next) {
|
||||
sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
|
||||
if (!IS_SOCKET_VALID(sock)) continue;
|
||||
if (connect(sock, addr->ai_addr, (int)addr->ai_addrlen) == 0) break;
|
||||
CLOSE_SOCKET(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
freeaddrinfo(addrs);
|
||||
|
||||
if (IS_SOCKET_VALID(sock)) {
|
||||
socketData->sock = sock;
|
||||
socketData->isListener = false;
|
||||
context->success = true;
|
||||
} else {
|
||||
context->success = false;
|
||||
context->errorMessage = "Connection failed.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SocketOp::ACCEPT: {
|
||||
if (socketData->isListener) {
|
||||
context->newSocket = accept(socketData->sock, nullptr, nullptr);
|
||||
context->success = IS_SOCKET_VALID(context->newSocket);
|
||||
if (!context->success) context->errorMessage = "Accept failed.";
|
||||
} else {
|
||||
context->success = false;
|
||||
context->errorMessage = "Cannot accept on a non-listening socket.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SocketOp::READ: {
|
||||
if (!socketData->isListener) {
|
||||
char buf[4096];
|
||||
ssize_t len = recv(socketData->sock, buf, sizeof(buf), 0);
|
||||
if (len > 0) {
|
||||
context->resultData.assign(buf, len);
|
||||
context->success = true;
|
||||
} else {
|
||||
context->success = false;
|
||||
context->errorMessage = "Read failed or connection closed.";
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SocketOp::WRITE: {
|
||||
if (!socketData->isListener) {
|
||||
ssize_t written = send(socketData->sock, context->data.c_str(), context->data.length(), 0);
|
||||
context->success = (written == (ssize_t)context->data.length());
|
||||
if(!context->success) context->errorMessage = "Write failed.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
completionQueue_.push(context);
|
||||
}
|
||||
}
|
||||
|
||||
WrenVM* vm_;
|
||||
std::atomic<bool> running_;
|
||||
std::vector<std::thread> threads_;
|
||||
ThreadSafeQueue<SocketContext*> requestQueue_;
|
||||
ThreadSafeQueue<SocketContext*> completionQueue_;
|
||||
};
|
||||
|
||||
static AsyncSocketManager* socketManager = nullptr;
|
||||
|
||||
// --- Socket Foreign Class/Methods ---
|
||||
|
||||
void socketAllocate(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(SocketData));
|
||||
data->sock = INVALID_SOCKET;
|
||||
data->isListener = false;
|
||||
}
|
||||
|
||||
void socketConnect(WrenVM* vm) {
|
||||
SocketContext* context = new SocketContext();
|
||||
context->operation = SocketOp::CONNECT;
|
||||
context->vm = vm;
|
||||
context->socketHandle = wrenGetSlotHandle(vm, 0);
|
||||
context->host = wrenGetSlotString(vm, 1);
|
||||
context->port = (int)wrenGetSlotDouble(vm, 2);
|
||||
context->callback = wrenGetSlotHandle(vm, 3);
|
||||
socketManager->submit(context);
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
|
||||
void socketListen(WrenVM* vm) {
|
||||
const char* host = wrenGetSlotString(vm, 1);
|
||||
int port = (int)wrenGetSlotDouble(vm, 2);
|
||||
int backlog = (int)wrenGetSlotDouble(vm, 3);
|
||||
|
||||
addrinfo hints = {}, *addrs;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
getaddrinfo(host, std::to_string(port).c_str(), &hints, &addrs);
|
||||
|
||||
socket_t sock = INVALID_SOCKET;
|
||||
for (addrinfo* addr = addrs; addr; addr = addr->ai_next) {
|
||||
sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
|
||||
if (!IS_SOCKET_VALID(sock)) continue;
|
||||
|
||||
int yes = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof(yes));
|
||||
|
||||
if (bind(sock, addr->ai_addr, (int)addr->ai_addrlen) == 0) break;
|
||||
|
||||
CLOSE_SOCKET(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
freeaddrinfo(addrs);
|
||||
|
||||
if (IS_SOCKET_VALID(sock) && listen(sock, backlog) == 0) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
data->sock = sock;
|
||||
data->isListener = true;
|
||||
wrenSetSlotBool(vm, 0, true);
|
||||
} else {
|
||||
if(IS_SOCKET_VALID(sock)) CLOSE_SOCKET(sock);
|
||||
wrenSetSlotBool(vm, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
void socketAccept(WrenVM* vm) {
|
||||
SocketContext* context = new SocketContext();
|
||||
context->operation = SocketOp::ACCEPT;
|
||||
context->vm = vm;
|
||||
context->socketHandle = wrenGetSlotHandle(vm, 0);
|
||||
context->callback = wrenGetSlotHandle(vm, 1);
|
||||
socketManager->submit(context);
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
|
||||
void socketRead(WrenVM* vm) {
|
||||
SocketContext* context = new SocketContext();
|
||||
context->operation = SocketOp::READ;
|
||||
context->vm = vm;
|
||||
context->socketHandle = wrenGetSlotHandle(vm, 0);
|
||||
context->bytesToRead = (int)wrenGetSlotDouble(vm, 1);
|
||||
context->callback = wrenGetSlotHandle(vm, 2);
|
||||
socketManager->submit(context);
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
|
||||
void socketWrite(WrenVM* vm) {
|
||||
SocketContext* context = new SocketContext();
|
||||
context->operation = SocketOp::WRITE;
|
||||
context->vm = vm;
|
||||
context->socketHandle = wrenGetSlotHandle(vm, 0);
|
||||
int len;
|
||||
const char* bytes = wrenGetSlotBytes(vm, 1, &len);
|
||||
context->data.assign(bytes, len);
|
||||
context->callback = wrenGetSlotHandle(vm, 2);
|
||||
socketManager->submit(context);
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
|
||||
void socketClose(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
if (IS_SOCKET_VALID(data->sock)) {
|
||||
CLOSE_SOCKET(data->sock);
|
||||
data->sock = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
void socketIsOpen(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotBool(vm, 0, IS_SOCKET_VALID(data->sock));
|
||||
}
|
||||
|
||||
WrenForeignMethodFn bindSocketForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) {
|
||||
if (strcmp(module, "socket") != 0) return NULL;
|
||||
if (strcmp(className, "Socket") == 0 && !isStatic) {
|
||||
if (strcmp(signature, "connect(_,_,_)") == 0) return socketConnect;
|
||||
if (strcmp(signature, "listen(_,_,_)") == 0) return socketListen;
|
||||
if (strcmp(signature, "accept(_)") == 0) return socketAccept;
|
||||
if (strcmp(signature, "read(_,_)") == 0) return socketRead;
|
||||
if (strcmp(signature, "write(_,_)") == 0) return socketWrite;
|
||||
if (strcmp(signature, "close()") == 0) return socketClose;
|
||||
if (strcmp(signature, "isOpen") == 0) return socketIsOpen;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WrenForeignClassMethods bindSocketForeignClass(WrenVM* vm, const char* module, const char* className) {
|
||||
WrenForeignClassMethods methods = {0};
|
||||
if (strcmp(module, "socket") == 0 && strcmp(className, "Socket") == 0) {
|
||||
methods.allocate = socketAllocate;
|
||||
}
|
||||
return methods;
|
||||
}
|
747
socket_backend_20250729_141833.c
Normal file
747
socket_backend_20250729_141833.c
Normal file
@ -0,0 +1,747 @@
|
||||
// socket_backend.c (Corrected with better handle safety and non-blocking I/O)
|
||||
#include "wren.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
// Platform-specific includes and definitions
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
typedef SOCKET socket_t;
|
||||
typedef int socklen_t;
|
||||
typedef HANDLE thread_t;
|
||||
typedef CRITICAL_SECTION mutex_t;
|
||||
typedef CONDITION_VARIABLE cond_t;
|
||||
#define IS_SOCKET_VALID(s) ((s) != INVALID_SOCKET)
|
||||
#define CLOSE_SOCKET(s) closesocket(s)
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <sys/select.h>
|
||||
typedef int socket_t;
|
||||
typedef pthread_t thread_t;
|
||||
typedef pthread_mutex_t mutex_t;
|
||||
typedef pthread_cond_t cond_t;
|
||||
#define INVALID_SOCKET -1
|
||||
#define IS_SOCKET_VALID(s) ((s) >= 0)
|
||||
#define CLOSE_SOCKET(s) close(s)
|
||||
#endif
|
||||
|
||||
// --- Forward Declarations ---
|
||||
typedef struct SocketContext SocketContext;
|
||||
|
||||
// --- Socket Data Structures ---
|
||||
|
||||
typedef enum {
|
||||
SOCKET_OP_CONNECT,
|
||||
SOCKET_OP_READ,
|
||||
SOCKET_OP_WRITE,
|
||||
} SocketOp;
|
||||
|
||||
typedef struct {
|
||||
socket_t sock;
|
||||
bool isListener;
|
||||
} SocketData;
|
||||
|
||||
struct SocketContext {
|
||||
SocketOp operation;
|
||||
WrenVM* vm;
|
||||
WrenHandle* socketHandle;
|
||||
WrenHandle* callback;
|
||||
|
||||
char* host;
|
||||
int port;
|
||||
char* data;
|
||||
size_t dataLength;
|
||||
|
||||
bool success;
|
||||
char* resultData;
|
||||
size_t resultDataLength;
|
||||
char* errorMessage;
|
||||
socket_t newSocket;
|
||||
struct SocketContext* next;
|
||||
};
|
||||
|
||||
// --- Thread-Safe Queue Implementation in C ---
|
||||
typedef struct {
|
||||
SocketContext *head, *tail;
|
||||
mutex_t mutex;
|
||||
cond_t cond;
|
||||
} ThreadSafeQueueSocket;
|
||||
|
||||
void queue_init(ThreadSafeQueueSocket* q) {
|
||||
q->head = q->tail = NULL;
|
||||
#ifdef _WIN32
|
||||
InitializeCriticalSection(&q->mutex);
|
||||
InitializeConditionVariable(&q->cond);
|
||||
#else
|
||||
pthread_mutex_init(&q->mutex, NULL);
|
||||
pthread_cond_init(&q->cond, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void queue_destroy(ThreadSafeQueueSocket* q) {
|
||||
#ifdef _WIN32
|
||||
DeleteCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_mutex_destroy(&q->mutex);
|
||||
pthread_cond_destroy(&q->cond);
|
||||
#endif
|
||||
}
|
||||
|
||||
void queue_push(ThreadSafeQueueSocket* q, SocketContext* context) {
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
#endif
|
||||
|
||||
if (context) {
|
||||
context->next = NULL;
|
||||
}
|
||||
|
||||
if (q->tail) {
|
||||
q->tail->next = context;
|
||||
} else {
|
||||
q->head = context;
|
||||
}
|
||||
q->tail = context;
|
||||
|
||||
#ifdef _WIN32
|
||||
WakeConditionVariable(&q->cond);
|
||||
LeaveCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_cond_signal(&q->cond);
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
SocketContext* queue_pop(ThreadSafeQueueSocket* q) {
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&q->mutex);
|
||||
while (q->head == NULL) {
|
||||
SleepConditionVariableCS(&q->cond, &q->mutex, INFINITE);
|
||||
}
|
||||
#else
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
while (q->head == NULL) {
|
||||
pthread_cond_wait(&q->cond, &q->mutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
SocketContext* context = q->head;
|
||||
q->head = q->head->next;
|
||||
if (q->head == NULL) {
|
||||
q->tail = NULL;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
#endif
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
bool queue_empty(ThreadSafeQueueSocket* q) {
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&q->mutex);
|
||||
bool empty = (q->head == NULL);
|
||||
LeaveCriticalSection(&q->mutex);
|
||||
#else
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
bool empty = (q->head == NULL);
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
#endif
|
||||
return empty;
|
||||
}
|
||||
|
||||
// --- Asynchronous Socket Manager ---
|
||||
|
||||
#define MAX_LISTENERS 64
|
||||
|
||||
typedef struct {
|
||||
WrenVM* vm;
|
||||
volatile bool running;
|
||||
thread_t worker_threads[4];
|
||||
thread_t listener_thread;
|
||||
|
||||
ThreadSafeQueueSocket requestQueue;
|
||||
ThreadSafeQueueSocket completionQueue;
|
||||
ThreadSafeQueueSocket acceptQueue;
|
||||
|
||||
mutex_t listener_mutex;
|
||||
socket_t listener_sockets[MAX_LISTENERS];
|
||||
int listener_count;
|
||||
#ifndef _WIN32
|
||||
socket_t wake_pipe[2];
|
||||
#endif
|
||||
} AsyncSocketManager;
|
||||
|
||||
static AsyncSocketManager* socketManager = NULL;
|
||||
|
||||
void free_socket_context_data(SocketContext* context) {
|
||||
if (!context) return;
|
||||
free(context->host);
|
||||
free(context->data);
|
||||
free(context->resultData);
|
||||
free(context->errorMessage);
|
||||
free(context);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD WINAPI workerThread(LPVOID arg);
|
||||
DWORD WINAPI listenerThread(LPVOID arg);
|
||||
#else
|
||||
void* workerThread(void* arg);
|
||||
void* listenerThread(void* arg);
|
||||
#endif
|
||||
|
||||
// --- Worker and Listener Thread Implementations ---
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD WINAPI listenerThread(LPVOID arg) {
|
||||
#else
|
||||
void* listenerThread(void* arg) {
|
||||
#endif
|
||||
AsyncSocketManager* manager = (AsyncSocketManager*)arg;
|
||||
|
||||
while (manager->running) {
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
|
||||
socket_t max_fd = 0;
|
||||
|
||||
#ifndef _WIN32
|
||||
FD_SET(manager->wake_pipe[0], &read_fds);
|
||||
max_fd = manager->wake_pipe[0];
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&manager->listener_mutex);
|
||||
#else
|
||||
pthread_mutex_lock(&manager->listener_mutex);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < manager->listener_count; i++) {
|
||||
socket_t sock = manager->listener_sockets[i];
|
||||
if (IS_SOCKET_VALID(sock)) {
|
||||
FD_SET(sock, &read_fds);
|
||||
if (sock > max_fd) {
|
||||
max_fd = sock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(&manager->listener_mutex);
|
||||
#else
|
||||
pthread_mutex_unlock(&manager->listener_mutex);
|
||||
#endif
|
||||
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
int activity = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);
|
||||
|
||||
if (!manager->running) break;
|
||||
if (activity < 0) {
|
||||
#ifndef _WIN32
|
||||
if (errno != EINTR) {
|
||||
perror("select error");
|
||||
}
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
if (activity == 0) continue;
|
||||
|
||||
#ifndef _WIN32
|
||||
if (FD_ISSET(manager->wake_pipe[0], &read_fds)) {
|
||||
char buffer[1];
|
||||
read(manager->wake_pipe[0], buffer, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&manager->listener_mutex);
|
||||
#else
|
||||
pthread_mutex_lock(&manager->listener_mutex);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < manager->listener_count; i++) {
|
||||
socket_t sock = manager->listener_sockets[i];
|
||||
if (IS_SOCKET_VALID(sock) && FD_ISSET(sock, &read_fds)) {
|
||||
if (!queue_empty(&manager->acceptQueue)) {
|
||||
SocketContext* context = queue_pop(&manager->acceptQueue);
|
||||
context->newSocket = accept(sock, NULL, NULL);
|
||||
context->success = IS_SOCKET_VALID(context->newSocket);
|
||||
if (!context->success) {
|
||||
context->errorMessage = strdup("Accept failed.");
|
||||
}
|
||||
queue_push(&manager->completionQueue, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(&manager->listener_mutex);
|
||||
#else
|
||||
pthread_mutex_unlock(&manager->listener_mutex);
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD WINAPI workerThread(LPVOID arg) {
|
||||
#else
|
||||
void* workerThread(void* arg) {
|
||||
#endif
|
||||
AsyncSocketManager* manager = (AsyncSocketManager*)arg;
|
||||
|
||||
while (manager->running) {
|
||||
SocketContext* context = queue_pop(&manager->requestQueue);
|
||||
if (!context || !manager->running) {
|
||||
if (context) free_socket_context_data(context);
|
||||
break;
|
||||
}
|
||||
|
||||
wrenEnsureSlots(context->vm, 1);
|
||||
wrenSetSlotHandle(context->vm, 0, context->socketHandle);
|
||||
SocketData* socketData = (wrenGetSlotType(context->vm, 0) == WREN_TYPE_FOREIGN)
|
||||
? (SocketData*)wrenGetSlotForeign(context->vm, 0)
|
||||
: NULL;
|
||||
|
||||
if (!socketData) {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Invalid socket object.");
|
||||
queue_push(&manager->completionQueue, context);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (context->operation) {
|
||||
case SOCKET_OP_CONNECT: {
|
||||
struct addrinfo hints = {0}, *addrs;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
char port_str[6];
|
||||
snprintf(port_str, 6, "%d", context->port);
|
||||
if (getaddrinfo(context->host, port_str, &hints, &addrs) != 0) {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Host lookup failed.");
|
||||
break;
|
||||
}
|
||||
|
||||
socket_t sock = INVALID_SOCKET;
|
||||
for (struct addrinfo* addr = addrs; addr; addr = addr->ai_next) {
|
||||
sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
|
||||
if (!IS_SOCKET_VALID(sock)) continue;
|
||||
if (connect(sock, addr->ai_addr, (int)addr->ai_addrlen) == 0) break;
|
||||
CLOSE_SOCKET(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
freeaddrinfo(addrs);
|
||||
|
||||
if (IS_SOCKET_VALID(sock)) {
|
||||
socketData->sock = sock;
|
||||
socketData->isListener = false;
|
||||
context->success = true;
|
||||
} else {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Connection failed.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCKET_OP_READ: {
|
||||
if (socketData->isListener) {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Cannot read from a listening socket.");
|
||||
break;
|
||||
}
|
||||
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(socketData->sock, &read_fds);
|
||||
struct timeval timeout = { .tv_sec = 5, .tv_usec = 0 }; // 5-second timeout
|
||||
|
||||
int activity = select(socketData->sock + 1, &read_fds, NULL, NULL, &timeout);
|
||||
if (activity > 0 && FD_ISSET(socketData->sock, &read_fds)) {
|
||||
char buf[4096];
|
||||
ssize_t len = recv(socketData->sock, buf, sizeof(buf), 0);
|
||||
if (len > 0) {
|
||||
context->resultData = (char*)malloc(len);
|
||||
memcpy(context->resultData, buf, len);
|
||||
context->resultDataLength = len;
|
||||
context->success = true;
|
||||
} else {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Read failed or connection closed.");
|
||||
}
|
||||
} else {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Read timeout or error.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCKET_OP_WRITE: {
|
||||
if (socketData->isListener) {
|
||||
context->success = false;
|
||||
context->errorMessage = strdup("Cannot write to a listening socket.");
|
||||
break;
|
||||
}
|
||||
ssize_t written = send(socketData->sock, context->data, context->dataLength, 0);
|
||||
context->success = (written == (ssize_t)context->dataLength);
|
||||
if(!context->success) context->errorMessage = strdup("Write failed.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
queue_push(&manager->completionQueue, context);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- Manager Lifecycle ---
|
||||
|
||||
void socketManager_create(WrenVM* vm) {
|
||||
socketManager = (AsyncSocketManager*)malloc(sizeof(AsyncSocketManager));
|
||||
socketManager->vm = vm;
|
||||
socketManager->running = true;
|
||||
socketManager->listener_count = 0;
|
||||
|
||||
queue_init(&socketManager->requestQueue);
|
||||
queue_init(&socketManager->completionQueue);
|
||||
queue_init(&socketManager->acceptQueue);
|
||||
|
||||
#ifdef _WIN32
|
||||
InitializeCriticalSection(&socketManager->listener_mutex);
|
||||
#else
|
||||
pthread_mutex_init(&socketManager->listener_mutex, NULL);
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
if (pipe(socketManager->wake_pipe) == -1) {
|
||||
perror("pipe");
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
#ifdef _WIN32
|
||||
socketManager->worker_threads[i] = CreateThread(NULL, 0, workerThread, socketManager, 0, NULL);
|
||||
#else
|
||||
pthread_create(&socketManager->worker_threads[i], NULL, workerThread, socketManager);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
socketManager->listener_thread = CreateThread(NULL, 0, listenerThread, socketManager, 0, NULL);
|
||||
#else
|
||||
pthread_create(&socketManager->listener_thread, NULL, listenerThread, socketManager);
|
||||
#endif
|
||||
}
|
||||
|
||||
void socketManager_destroy() {
|
||||
socketManager->running = false;
|
||||
|
||||
#ifndef _WIN32
|
||||
write(socketManager->wake_pipe[1], "w", 1);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
queue_push(&socketManager->requestQueue, NULL);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
WaitForSingleObject(socketManager->listener_thread, INFINITE);
|
||||
CloseHandle(socketManager->listener_thread);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
WaitForSingleObject(socketManager->worker_threads[i], INFINITE);
|
||||
CloseHandle(socketManager->worker_threads[i]);
|
||||
}
|
||||
#else
|
||||
pthread_join(socketManager->listener_thread, NULL);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
pthread_join(socketManager->worker_threads[i], NULL);
|
||||
}
|
||||
close(socketManager->wake_pipe[0]);
|
||||
close(socketManager->wake_pipe[1]);
|
||||
#endif
|
||||
|
||||
queue_destroy(&socketManager->requestQueue);
|
||||
queue_destroy(&socketManager->completionQueue);
|
||||
queue_destroy(&socketManager->acceptQueue);
|
||||
|
||||
#ifdef _WIN32
|
||||
DeleteCriticalSection(&socketManager->listener_mutex);
|
||||
#else
|
||||
pthread_mutex_destroy(&socketManager->listener_mutex);
|
||||
#endif
|
||||
|
||||
free(socketManager);
|
||||
}
|
||||
|
||||
void socketManager_processCompletions() {
|
||||
WrenHandle* callHandle = wrenMakeCallHandle(socketManager->vm, "call(_,_)");
|
||||
while (!queue_empty(&socketManager->completionQueue)) {
|
||||
SocketContext* context = queue_pop(&socketManager->completionQueue);
|
||||
|
||||
wrenEnsureSlots(socketManager->vm, 3);
|
||||
wrenSetSlotHandle(socketManager->vm, 0, context->callback);
|
||||
if (context->success) {
|
||||
wrenSetSlotNull(socketManager->vm, 1);
|
||||
if (IS_SOCKET_VALID(context->newSocket)) {
|
||||
wrenGetVariable(socketManager->vm, "socket", "Socket", 2);
|
||||
void* foreign = wrenSetSlotNewForeign(socketManager->vm, 2, 2, sizeof(SocketData));
|
||||
SocketData* clientData = (SocketData*)foreign;
|
||||
clientData->sock = context->newSocket;
|
||||
clientData->isListener = false;
|
||||
} else if (context->resultData) {
|
||||
wrenSetSlotBytes(socketManager->vm, 2, context->resultData, context->resultDataLength);
|
||||
} else {
|
||||
wrenSetSlotNull(socketManager->vm, 2);
|
||||
}
|
||||
} else {
|
||||
wrenSetSlotString(socketManager->vm, 1, context->errorMessage ? context->errorMessage : "Unknown error.");
|
||||
wrenSetSlotNull(socketManager->vm, 2);
|
||||
}
|
||||
|
||||
wrenCall(socketManager->vm, callHandle);
|
||||
|
||||
// Safely release handles here on the main thread
|
||||
wrenReleaseHandle(socketManager->vm, context->socketHandle);
|
||||
wrenReleaseHandle(socketManager->vm, context->callback);
|
||||
free_socket_context_data(context);
|
||||
}
|
||||
wrenReleaseHandle(socketManager->vm, callHandle);
|
||||
}
|
||||
|
||||
// ... (The rest of the foreign functions from socketAllocate onwards are identical to the previous response) ...
|
||||
void socketAllocate(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(SocketData));
|
||||
data->sock = INVALID_SOCKET;
|
||||
data->isListener = false;
|
||||
}
|
||||
|
||||
void socketConnect(WrenVM* vm) {
|
||||
SocketContext* context = (SocketContext*)calloc(1, sizeof(SocketContext));
|
||||
context->operation = SOCKET_OP_CONNECT;
|
||||
context->vm = vm;
|
||||
context->socketHandle = wrenGetSlotHandle(vm, 0);
|
||||
context->host = strdup(wrenGetSlotString(vm, 1));
|
||||
context->port = (int)wrenGetSlotDouble(vm, 2);
|
||||
context->callback = wrenGetSlotHandle(vm, 3);
|
||||
queue_push(&socketManager->requestQueue, context);
|
||||
}
|
||||
|
||||
void socketListen(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
const char* host = wrenGetSlotString(vm, 1);
|
||||
int port = (int)wrenGetSlotDouble(vm, 2);
|
||||
int backlog = (int)wrenGetSlotDouble(vm, 3);
|
||||
|
||||
struct addrinfo hints = {0}, *addrs;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
char port_str[6];
|
||||
snprintf(port_str, 6, "%d", port);
|
||||
if (getaddrinfo(host, port_str, &hints, &addrs) != 0) {
|
||||
wrenSetSlotBool(vm, 0, false);
|
||||
return;
|
||||
}
|
||||
|
||||
socket_t sock = INVALID_SOCKET;
|
||||
for (struct addrinfo* addr = addrs; addr; addr = addr->ai_next) {
|
||||
sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
|
||||
if (!IS_SOCKET_VALID(sock)) continue;
|
||||
|
||||
int yes = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof(yes));
|
||||
|
||||
if (bind(sock, addr->ai_addr, (int)addr->ai_addrlen) == 0) break;
|
||||
|
||||
CLOSE_SOCKET(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
freeaddrinfo(addrs);
|
||||
|
||||
if (IS_SOCKET_VALID(sock) && listen(sock, backlog) == 0) {
|
||||
data->sock = sock;
|
||||
data->isListener = true;
|
||||
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&socketManager->listener_mutex);
|
||||
#else
|
||||
pthread_mutex_lock(&socketManager->listener_mutex);
|
||||
#endif
|
||||
|
||||
if (socketManager->listener_count < MAX_LISTENERS) {
|
||||
socketManager->listener_sockets[socketManager->listener_count++] = sock;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(&socketManager->listener_mutex);
|
||||
#else
|
||||
pthread_mutex_unlock(&socketManager->listener_mutex);
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
write(socketManager->wake_pipe[1], "w", 1);
|
||||
#endif
|
||||
|
||||
wrenSetSlotBool(vm, 0, true);
|
||||
} else {
|
||||
if(IS_SOCKET_VALID(sock)) CLOSE_SOCKET(sock);
|
||||
wrenSetSlotBool(vm, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
void socketAccept(WrenVM* vm) {
|
||||
SocketContext* context = (SocketContext*)calloc(1, sizeof(SocketContext));
|
||||
context->vm = vm;
|
||||
context->socketHandle = wrenGetSlotHandle(vm, 0);
|
||||
context->callback = wrenGetSlotHandle(vm, 1);
|
||||
queue_push(&socketManager->acceptQueue, context);
|
||||
}
|
||||
|
||||
void socketRead(WrenVM* vm) {
|
||||
SocketContext* context = (SocketContext*)calloc(1, sizeof(SocketContext));
|
||||
context->operation = SOCKET_OP_READ;
|
||||
context->vm = vm;
|
||||
context->socketHandle = wrenGetSlotHandle(vm, 0);
|
||||
context->callback = wrenGetSlotHandle(vm, 1);
|
||||
queue_push(&socketManager->requestQueue, context);
|
||||
}
|
||||
|
||||
void socketWrite(WrenVM* vm) {
|
||||
SocketContext* context = (SocketContext*)calloc(1, sizeof(SocketContext));
|
||||
context->operation = SOCKET_OP_WRITE;
|
||||
context->vm = vm;
|
||||
context->socketHandle = wrenGetSlotHandle(vm, 0);
|
||||
int len;
|
||||
const char* bytes = wrenGetSlotBytes(vm, 1, &len);
|
||||
context->data = (char*)malloc(len);
|
||||
memcpy(context->data, bytes, len);
|
||||
context->dataLength = len;
|
||||
context->callback = wrenGetSlotHandle(vm, 2);
|
||||
queue_push(&socketManager->requestQueue, context);
|
||||
}
|
||||
|
||||
void socketClose(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
if (IS_SOCKET_VALID(data->sock)) {
|
||||
if (data->isListener) {
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&socketManager->listener_mutex);
|
||||
#else
|
||||
pthread_mutex_lock(&socketManager->listener_mutex);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < socketManager->listener_count; i++) {
|
||||
if (socketManager->listener_sockets[i] == data->sock) {
|
||||
socketManager->listener_sockets[i] = socketManager->listener_sockets[socketManager->listener_count - 1];
|
||||
socketManager->listener_count--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(&socketManager->listener_mutex);
|
||||
#else
|
||||
pthread_mutex_unlock(&socketManager->listener_mutex);
|
||||
#endif
|
||||
}
|
||||
CLOSE_SOCKET(data->sock);
|
||||
data->sock = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
void socketIsOpen(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotBool(vm, 0, IS_SOCKET_VALID(data->sock));
|
||||
}
|
||||
|
||||
void socketRemoteAddress(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
if (!IS_SOCKET_VALID(data->sock) || data->isListener) {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
char ipstr[INET6_ADDRSTRLEN];
|
||||
|
||||
if (getpeername(data->sock, (struct sockaddr*)&addr, &len) == 0) {
|
||||
if (addr.ss_family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((struct sockaddr_in*)&addr)->sin_addr, ipstr, sizeof(ipstr));
|
||||
} else {
|
||||
inet_ntop(AF_INET6, &((struct sockaddr_in6*)&addr)->sin6_addr, ipstr, sizeof(ipstr));
|
||||
}
|
||||
wrenSetSlotString(vm, 0, ipstr);
|
||||
} else {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void socketRemotePort(WrenVM* vm) {
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
if (!IS_SOCKET_VALID(data->sock) || data->isListener) {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
|
||||
if (getpeername(data->sock, (struct sockaddr*)&addr, &len) == 0) {
|
||||
int port = 0;
|
||||
if (addr.ss_family == AF_INET) {
|
||||
port = ntohs(((struct sockaddr_in*)&addr)->sin_port);
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
port = ntohs(((struct sockaddr_in6*)&addr)->sin6_port);
|
||||
}
|
||||
wrenSetSlotDouble(vm, 0, (double)port);
|
||||
} else {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
}
|
||||
|
||||
WrenForeignMethodFn bindSocketForeignMethod(WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) {
|
||||
if (strcmp(module, "socket") != 0) return NULL;
|
||||
if (strcmp(className, "Socket") == 0 && !isStatic) {
|
||||
if (strcmp(signature, "connect(_,_,_)") == 0) return socketConnect;
|
||||
if (strcmp(signature, "listen(_,_,_)") == 0) return socketListen;
|
||||
if (strcmp(signature, "accept(_)") == 0) return socketAccept;
|
||||
// NOTE: The signature for read() in Wren takes one argument (the callback) now.
|
||||
if (strcmp(signature, "read(_)") == 0) return socketRead;
|
||||
if (strcmp(signature, "write_(_,_)") == 0) return socketWrite;
|
||||
if (strcmp(signature, "close()") == 0) return socketClose;
|
||||
if (strcmp(signature, "isOpen") == 0) return socketIsOpen;
|
||||
if (strcmp(signature, "remoteAddress") == 0) return socketRemoteAddress;
|
||||
if (strcmp(signature, "remotePort") == 0) return socketRemotePort;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WrenForeignClassMethods bindSocketForeignClass(WrenVM* vm, const char* module, const char* className) {
|
||||
WrenForeignClassMethods methods = {0, 0};
|
||||
if (strcmp(module, "socket") == 0 && strcmp(className, "Socket") == 0) {
|
||||
methods.allocate = socketAllocate;
|
||||
}
|
||||
return methods;
|
||||
}
|
81
socket_example.wren
Normal file
81
socket_example.wren
Normal file
@ -0,0 +1,81 @@
|
||||
// socket_example.wren (Corrected)
|
||||
import "socket" for Socket
|
||||
|
||||
System.print("--- Wren Socket Echo Server and Client ---")
|
||||
|
||||
var serverFiber = Fiber.new {
|
||||
var server = Socket.new()
|
||||
if (server.listen("localhost", 8080, 5)) {
|
||||
System.print("Server listening on localhost:8080")
|
||||
while (server.isOpen) {
|
||||
server.accept { |err, client|
|
||||
|
||||
if (err) {
|
||||
System.print("Accept error: %(err)")
|
||||
return
|
||||
}
|
||||
|
||||
System.print("Client connected!")
|
||||
Fiber.new {
|
||||
while (client.isOpen) {
|
||||
client.read(4096) { |readErr, data|
|
||||
if (readErr) {
|
||||
System.print("Client disconnected.")
|
||||
client.close()
|
||||
return
|
||||
}
|
||||
System.print("Received: %(data)")
|
||||
// CORRECTED: Replaced '_' with 'result'
|
||||
client.write("Echo: %(data)") { |writeErr, result|
|
||||
if (writeErr) System.print("Write error: %(writeErr)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.call()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.print("Failed to start server.")
|
||||
}
|
||||
}
|
||||
|
||||
var clientFiber = Fiber.new {
|
||||
var client = Socket.new()
|
||||
// CORRECTED: Replaced '_' with 'result'
|
||||
client.connect("localhost", 8080) { |err, result|
|
||||
if (err) {
|
||||
System.print("Client connection error: %(err)")
|
||||
return
|
||||
}
|
||||
|
||||
System.print("Client connected to server.")
|
||||
// CORRECTED: Replaced '_' with 'result'
|
||||
client.write("Hello from Wren!") { |writeErr, result|
|
||||
if (writeErr) {
|
||||
System.print("Client write error: %(writeErr)")
|
||||
return
|
||||
}
|
||||
|
||||
client.read(1024) { |readErr, data|
|
||||
if (readErr) {
|
||||
System.print("Client read error: %(readErr)")
|
||||
} else {
|
||||
System.print("Client received: %(data)")
|
||||
}
|
||||
client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start the server
|
||||
serverFiber.call()
|
||||
|
||||
// Give the server a moment to start up before connecting the client
|
||||
Fiber.sleep(100)
|
||||
|
||||
// Start the client
|
||||
clientFiber.call()
|
||||
|
||||
// Let the operations complete
|
||||
Fiber.sleep(1000)
|
554
wren.h
Normal file
554
wren.h
Normal file
@ -0,0 +1,554 @@
|
||||
#ifndef wren_h
|
||||
#define wren_h
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// The Wren semantic version number components.
|
||||
#define WREN_VERSION_MAJOR 0
|
||||
#define WREN_VERSION_MINOR 4
|
||||
#define WREN_VERSION_PATCH 0
|
||||
|
||||
// A human-friendly string representation of the version.
|
||||
#define WREN_VERSION_STRING "0.4.0"
|
||||
|
||||
// A monotonically increasing numeric representation of the version number. Use
|
||||
// this if you want to do range checks over versions.
|
||||
#define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \
|
||||
WREN_VERSION_MINOR * 1000 + \
|
||||
WREN_VERSION_PATCH)
|
||||
|
||||
#ifndef WREN_API
|
||||
#if defined(_MSC_VER) && defined(WREN_API_DLLEXPORT)
|
||||
#define WREN_API __declspec( dllexport )
|
||||
#else
|
||||
#define WREN_API
|
||||
#endif
|
||||
#endif //WREN_API
|
||||
|
||||
// A single virtual machine for executing Wren code.
|
||||
//
|
||||
// Wren has no global state, so all state stored by a running interpreter lives
|
||||
// here.
|
||||
typedef struct WrenVM WrenVM;
|
||||
|
||||
// A handle to a Wren object.
|
||||
//
|
||||
// This lets code outside of the VM hold a persistent reference to an object.
|
||||
// After a handle is acquired, and until it is released, this ensures the
|
||||
// garbage collector will not reclaim the object it references.
|
||||
typedef struct WrenHandle WrenHandle;
|
||||
|
||||
// A generic allocation function that handles all explicit memory management
|
||||
// used by Wren. It's used like so:
|
||||
//
|
||||
// - To allocate new memory, [memory] is NULL and [newSize] is the desired
|
||||
// size. It should return the allocated memory or NULL on failure.
|
||||
//
|
||||
// - To attempt to grow an existing allocation, [memory] is the memory, and
|
||||
// [newSize] is the desired size. It should return [memory] if it was able to
|
||||
// grow it in place, or a new pointer if it had to move it.
|
||||
//
|
||||
// - To shrink memory, [memory] and [newSize] are the same as above but it will
|
||||
// always return [memory].
|
||||
//
|
||||
// - To free memory, [memory] will be the memory to free and [newSize] will be
|
||||
// zero. It should return NULL.
|
||||
typedef void* (*WrenReallocateFn)(void* memory, size_t newSize, void* userData);
|
||||
|
||||
// A function callable from Wren code, but implemented in C.
|
||||
typedef void (*WrenForeignMethodFn)(WrenVM* vm);
|
||||
|
||||
// A finalizer function for freeing resources owned by an instance of a foreign
|
||||
// class. Unlike most foreign methods, finalizers do not have access to the VM
|
||||
// and should not interact with it since it's in the middle of a garbage
|
||||
// collection.
|
||||
typedef void (*WrenFinalizerFn)(void* data);
|
||||
|
||||
// Gives the host a chance to canonicalize the imported module name,
|
||||
// potentially taking into account the (previously resolved) name of the module
|
||||
// that contains the import. Typically, this is used to implement relative
|
||||
// imports.
|
||||
typedef const char* (*WrenResolveModuleFn)(WrenVM* vm,
|
||||
const char* importer, const char* name);
|
||||
|
||||
// Forward declare
|
||||
struct WrenLoadModuleResult;
|
||||
|
||||
// Called after loadModuleFn is called for module [name]. The original returned result
|
||||
// is handed back to you in this callback, so that you can free memory if appropriate.
|
||||
typedef void (*WrenLoadModuleCompleteFn)(WrenVM* vm, const char* name, struct WrenLoadModuleResult result);
|
||||
|
||||
// The result of a loadModuleFn call.
|
||||
// [source] is the source code for the module, or NULL if the module is not found.
|
||||
// [onComplete] an optional callback that will be called once Wren is done with the result.
|
||||
typedef struct WrenLoadModuleResult
|
||||
{
|
||||
const char* source;
|
||||
WrenLoadModuleCompleteFn onComplete;
|
||||
void* userData;
|
||||
} WrenLoadModuleResult;
|
||||
|
||||
// Loads and returns the source code for the module [name].
|
||||
typedef WrenLoadModuleResult (*WrenLoadModuleFn)(WrenVM* vm, const char* name);
|
||||
|
||||
// Returns a pointer to a foreign method on [className] in [module] with
|
||||
// [signature].
|
||||
typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm,
|
||||
const char* module, const char* className, bool isStatic,
|
||||
const char* signature);
|
||||
|
||||
// Displays a string of text to the user.
|
||||
typedef void (*WrenWriteFn)(WrenVM* vm, const char* text);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
// A syntax or resolution error detected at compile time.
|
||||
WREN_ERROR_COMPILE,
|
||||
|
||||
// The error message for a runtime error.
|
||||
WREN_ERROR_RUNTIME,
|
||||
|
||||
// One entry of a runtime error's stack trace.
|
||||
WREN_ERROR_STACK_TRACE
|
||||
} WrenErrorType;
|
||||
|
||||
// Reports an error to the user.
|
||||
//
|
||||
// An error detected during compile time is reported by calling this once with
|
||||
// [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line]
|
||||
// where the error occurs, and the compiler's error [message].
|
||||
//
|
||||
// A runtime error is reported by calling this once with [type]
|
||||
// `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's
|
||||
// [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are
|
||||
// made for each line in the stack trace. Each of those has the resolved
|
||||
// [module] and [line] where the method or function is defined and [message] is
|
||||
// the name of the method or function.
|
||||
typedef void (*WrenErrorFn)(
|
||||
WrenVM* vm, WrenErrorType type, const char* module, int line,
|
||||
const char* message);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// The callback invoked when the foreign object is created.
|
||||
//
|
||||
// This must be provided. Inside the body of this, it must call
|
||||
// [wrenSetSlotNewForeign()] exactly once.
|
||||
WrenForeignMethodFn allocate;
|
||||
|
||||
// The callback invoked when the garbage collector is about to collect a
|
||||
// foreign object's memory.
|
||||
//
|
||||
// This may be `NULL` if the foreign class does not need to finalize.
|
||||
WrenFinalizerFn finalize;
|
||||
} WrenForeignClassMethods;
|
||||
|
||||
// Returns a pair of pointers to the foreign methods used to allocate and
|
||||
// finalize the data for instances of [className] in resolved [module].
|
||||
typedef WrenForeignClassMethods (*WrenBindForeignClassFn)(
|
||||
WrenVM* vm, const char* module, const char* className);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// The callback Wren will use to allocate, reallocate, and deallocate memory.
|
||||
//
|
||||
// If `NULL`, defaults to a built-in function that uses `realloc` and `free`.
|
||||
WrenReallocateFn reallocateFn;
|
||||
|
||||
// The callback Wren uses to resolve a module name.
|
||||
//
|
||||
// Some host applications may wish to support "relative" imports, where the
|
||||
// meaning of an import string depends on the module that contains it. To
|
||||
// support that without baking any policy into Wren itself, the VM gives the
|
||||
// host a chance to resolve an import string.
|
||||
//
|
||||
// Before an import is loaded, it calls this, passing in the name of the
|
||||
// module that contains the import and the import string. The host app can
|
||||
// look at both of those and produce a new "canonical" string that uniquely
|
||||
// identifies the module. This string is then used as the name of the module
|
||||
// going forward. It is what is passed to [loadModuleFn], how duplicate
|
||||
// imports of the same module are detected, and how the module is reported in
|
||||
// stack traces.
|
||||
//
|
||||
// If you leave this function NULL, then the original import string is
|
||||
// treated as the resolved string.
|
||||
//
|
||||
// If an import cannot be resolved by the embedder, it should return NULL and
|
||||
// Wren will report that as a runtime error.
|
||||
//
|
||||
// Wren will take ownership of the string you return and free it for you, so
|
||||
// it should be allocated using the same allocation function you provide
|
||||
// above.
|
||||
WrenResolveModuleFn resolveModuleFn;
|
||||
|
||||
// The callback Wren uses to load a module.
|
||||
//
|
||||
// Since Wren does not talk directly to the file system, it relies on the
|
||||
// embedder to physically locate and read the source code for a module. The
|
||||
// first time an import appears, Wren will call this and pass in the name of
|
||||
// the module being imported. The method will return a result, which contains
|
||||
// the source code for that module. Memory for the source is owned by the
|
||||
// host application, and can be freed using the onComplete callback.
|
||||
//
|
||||
// This will only be called once for any given module name. Wren caches the
|
||||
// result internally so subsequent imports of the same module will use the
|
||||
// previous source and not call this.
|
||||
//
|
||||
// If a module with the given name could not be found by the embedder, it
|
||||
// should return NULL and Wren will report that as a runtime error.
|
||||
WrenLoadModuleFn loadModuleFn;
|
||||
|
||||
// The callback Wren uses to find a foreign method and bind it to a class.
|
||||
//
|
||||
// When a foreign method is declared in a class, this will be called with the
|
||||
// foreign method's module, class, and signature when the class body is
|
||||
// executed. It should return a pointer to the foreign function that will be
|
||||
// bound to that method.
|
||||
//
|
||||
// If the foreign function could not be found, this should return NULL and
|
||||
// Wren will report it as runtime error.
|
||||
WrenBindForeignMethodFn bindForeignMethodFn;
|
||||
|
||||
// The callback Wren uses to find a foreign class and get its foreign methods.
|
||||
//
|
||||
// When a foreign class is declared, this will be called with the class's
|
||||
// module and name when the class body is executed. It should return the
|
||||
// foreign functions uses to allocate and (optionally) finalize the bytes
|
||||
// stored in the foreign object when an instance is created.
|
||||
WrenBindForeignClassFn bindForeignClassFn;
|
||||
|
||||
// The callback Wren uses to display text when `System.print()` or the other
|
||||
// related functions are called.
|
||||
//
|
||||
// If this is `NULL`, Wren discards any printed text.
|
||||
WrenWriteFn writeFn;
|
||||
|
||||
// The callback Wren uses to report errors.
|
||||
//
|
||||
// When an error occurs, this will be called with the module name, line
|
||||
// number, and an error message. If this is `NULL`, Wren doesn't report any
|
||||
// errors.
|
||||
WrenErrorFn errorFn;
|
||||
|
||||
// The number of bytes Wren will allocate before triggering the first garbage
|
||||
// collection.
|
||||
//
|
||||
// If zero, defaults to 10MB.
|
||||
size_t initialHeapSize;
|
||||
|
||||
// After a collection occurs, the threshold for the next collection is
|
||||
// determined based on the number of bytes remaining in use. This allows Wren
|
||||
// to shrink its memory usage automatically after reclaiming a large amount
|
||||
// of memory.
|
||||
//
|
||||
// This can be used to ensure that the heap does not get too small, which can
|
||||
// in turn lead to a large number of collections afterwards as the heap grows
|
||||
// back to a usable size.
|
||||
//
|
||||
// If zero, defaults to 1MB.
|
||||
size_t minHeapSize;
|
||||
|
||||
// Wren will resize the heap automatically as the number of bytes
|
||||
// remaining in use after a collection changes. This number determines the
|
||||
// amount of additional memory Wren will use after a collection, as a
|
||||
// percentage of the current heap size.
|
||||
//
|
||||
// For example, say that this is 50. After a garbage collection, when there
|
||||
// are 400 bytes of memory still in use, the next collection will be triggered
|
||||
// after a total of 600 bytes are allocated (including the 400 already in
|
||||
// use.)
|
||||
//
|
||||
// Setting this to a smaller number wastes less memory, but triggers more
|
||||
// frequent garbage collections.
|
||||
//
|
||||
// If zero, defaults to 50.
|
||||
int heapGrowthPercent;
|
||||
|
||||
// User-defined data associated with the VM.
|
||||
void* userData;
|
||||
|
||||
} WrenConfiguration;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
WREN_RESULT_SUCCESS,
|
||||
WREN_RESULT_COMPILE_ERROR,
|
||||
WREN_RESULT_RUNTIME_ERROR
|
||||
} WrenInterpretResult;
|
||||
|
||||
// The type of an object stored in a slot.
|
||||
//
|
||||
// This is not necessarily the object's *class*, but instead its low level
|
||||
// representation type.
|
||||
typedef enum
|
||||
{
|
||||
WREN_TYPE_BOOL,
|
||||
WREN_TYPE_NUM,
|
||||
WREN_TYPE_FOREIGN,
|
||||
WREN_TYPE_LIST,
|
||||
WREN_TYPE_MAP,
|
||||
WREN_TYPE_NULL,
|
||||
WREN_TYPE_STRING,
|
||||
|
||||
// The object is of a type that isn't accessible by the C API.
|
||||
WREN_TYPE_UNKNOWN
|
||||
} WrenType;
|
||||
|
||||
// Get the current wren version number.
|
||||
//
|
||||
// Can be used to range checks over versions.
|
||||
WREN_API int wrenGetVersionNumber();
|
||||
|
||||
// Initializes [configuration] with all of its default values.
|
||||
//
|
||||
// Call this before setting the particular fields you care about.
|
||||
WREN_API void wrenInitConfiguration(WrenConfiguration* configuration);
|
||||
|
||||
// Creates a new Wren virtual machine using the given [configuration]. Wren
|
||||
// will copy the configuration data, so the argument passed to this can be
|
||||
// freed after calling this. If [configuration] is `NULL`, uses a default
|
||||
// configuration.
|
||||
WREN_API WrenVM* wrenNewVM(WrenConfiguration* configuration);
|
||||
|
||||
// Disposes of all resources is use by [vm], which was previously created by a
|
||||
// call to [wrenNewVM].
|
||||
WREN_API void wrenFreeVM(WrenVM* vm);
|
||||
|
||||
// Immediately run the garbage collector to free unused memory.
|
||||
WREN_API void wrenCollectGarbage(WrenVM* vm);
|
||||
|
||||
// Runs [source], a string of Wren source code in a new fiber in [vm] in the
|
||||
// context of resolved [module].
|
||||
WREN_API WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
|
||||
const char* source);
|
||||
|
||||
// Creates a handle that can be used to invoke a method with [signature] on
|
||||
// using a receiver and arguments that are set up on the stack.
|
||||
//
|
||||
// This handle can be used repeatedly to directly invoke that method from C
|
||||
// code using [wrenCall].
|
||||
//
|
||||
// When you are done with this handle, it must be released using
|
||||
// [wrenReleaseHandle].
|
||||
WREN_API WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature);
|
||||
|
||||
// Calls [method], using the receiver and arguments previously set up on the
|
||||
// stack.
|
||||
//
|
||||
// [method] must have been created by a call to [wrenMakeCallHandle]. The
|
||||
// arguments to the method must be already on the stack. The receiver should be
|
||||
// in slot 0 with the remaining arguments following it, in order. It is an
|
||||
// error if the number of arguments provided does not match the method's
|
||||
// signature.
|
||||
//
|
||||
// After this returns, you can access the return value from slot 0 on the stack.
|
||||
WREN_API WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method);
|
||||
|
||||
// Releases the reference stored in [handle]. After calling this, [handle] can
|
||||
// no longer be used.
|
||||
WREN_API void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle);
|
||||
|
||||
// The following functions are intended to be called from foreign methods or
|
||||
// finalizers. The interface Wren provides to a foreign method is like a
|
||||
// register machine: you are given a numbered array of slots that values can be
|
||||
// read from and written to. Values always live in a slot (unless explicitly
|
||||
// captured using wrenGetSlotHandle(), which ensures the garbage collector can
|
||||
// find them.
|
||||
//
|
||||
// When your foreign function is called, you are given one slot for the receiver
|
||||
// and each argument to the method. The receiver is in slot 0 and the arguments
|
||||
// are in increasingly numbered slots after that. You are free to read and
|
||||
// write to those slots as you want. If you want more slots to use as scratch
|
||||
// space, you can call wrenEnsureSlots() to add more.
|
||||
//
|
||||
// When your function returns, every slot except slot zero is discarded and the
|
||||
// value in slot zero is used as the return value of the method. If you don't
|
||||
// store a return value in that slot yourself, it will retain its previous
|
||||
// value, the receiver.
|
||||
//
|
||||
// While Wren is dynamically typed, C is not. This means the C interface has to
|
||||
// support the various types of primitive values a Wren variable can hold: bool,
|
||||
// double, string, etc. If we supported this for every operation in the C API,
|
||||
// there would be a combinatorial explosion of functions, like "get a
|
||||
// double-valued element from a list", "insert a string key and double value
|
||||
// into a map", etc.
|
||||
//
|
||||
// To avoid that, the only way to convert to and from a raw C value is by going
|
||||
// into and out of a slot. All other functions work with values already in a
|
||||
// slot. So, to add an element to a list, you put the list in one slot, and the
|
||||
// element in another. Then there is a single API function wrenInsertInList()
|
||||
// that takes the element out of that slot and puts it into the list.
|
||||
//
|
||||
// The goal of this API is to be easy to use while not compromising performance.
|
||||
// The latter means it does not do type or bounds checking at runtime except
|
||||
// using assertions which are generally removed from release builds. C is an
|
||||
// unsafe language, so it's up to you to be careful to use it correctly. In
|
||||
// return, you get a very fast FFI.
|
||||
|
||||
// Returns the number of slots available to the current foreign method.
|
||||
WREN_API int wrenGetSlotCount(WrenVM* vm);
|
||||
|
||||
// Ensures that the foreign method stack has at least [numSlots] available for
|
||||
// use, growing the stack if needed.
|
||||
//
|
||||
// Does not shrink the stack if it has more than enough slots.
|
||||
//
|
||||
// It is an error to call this from a finalizer.
|
||||
WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots);
|
||||
|
||||
// Gets the type of the object in [slot].
|
||||
WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot);
|
||||
|
||||
// Reads a boolean value from [slot].
|
||||
//
|
||||
// It is an error to call this if the slot does not contain a boolean value.
|
||||
WREN_API bool wrenGetSlotBool(WrenVM* vm, int slot);
|
||||
|
||||
// Reads a byte array from [slot].
|
||||
//
|
||||
// The memory for the returned string is owned by Wren. You can inspect it
|
||||
// while in your foreign method, but cannot keep a pointer to it after the
|
||||
// function returns, since the garbage collector may reclaim it.
|
||||
//
|
||||
// Returns a pointer to the first byte of the array and fill [length] with the
|
||||
// number of bytes in the array.
|
||||
//
|
||||
// It is an error to call this if the slot does not contain a string.
|
||||
WREN_API const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length);
|
||||
|
||||
// Reads a number from [slot].
|
||||
//
|
||||
// It is an error to call this if the slot does not contain a number.
|
||||
WREN_API double wrenGetSlotDouble(WrenVM* vm, int slot);
|
||||
|
||||
// Reads a foreign object from [slot] and returns a pointer to the foreign data
|
||||
// stored with it.
|
||||
//
|
||||
// It is an error to call this if the slot does not contain an instance of a
|
||||
// foreign class.
|
||||
WREN_API void* wrenGetSlotForeign(WrenVM* vm, int slot);
|
||||
|
||||
// Reads a string from [slot].
|
||||
//
|
||||
// The memory for the returned string is owned by Wren. You can inspect it
|
||||
// while in your foreign method, but cannot keep a pointer to it after the
|
||||
// function returns, since the garbage collector may reclaim it.
|
||||
//
|
||||
// It is an error to call this if the slot does not contain a string.
|
||||
WREN_API const char* wrenGetSlotString(WrenVM* vm, int slot);
|
||||
|
||||
// Creates a handle for the value stored in [slot].
|
||||
//
|
||||
// This will prevent the object that is referred to from being garbage collected
|
||||
// until the handle is released by calling [wrenReleaseHandle()].
|
||||
WREN_API WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot);
|
||||
|
||||
// Stores the boolean [value] in [slot].
|
||||
WREN_API void wrenSetSlotBool(WrenVM* vm, int slot, bool value);
|
||||
|
||||
// Stores the array [length] of [bytes] in [slot].
|
||||
//
|
||||
// The bytes are copied to a new string within Wren's heap, so you can free
|
||||
// memory used by them after this is called.
|
||||
WREN_API void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length);
|
||||
|
||||
// Stores the numeric [value] in [slot].
|
||||
WREN_API void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
|
||||
|
||||
// Creates a new instance of the foreign class stored in [classSlot] with [size]
|
||||
// bytes of raw storage and places the resulting object in [slot].
|
||||
//
|
||||
// This does not invoke the foreign class's constructor on the new instance. If
|
||||
// you need that to happen, call the constructor from Wren, which will then
|
||||
// call the allocator foreign method. In there, call this to create the object
|
||||
// and then the constructor will be invoked when the allocator returns.
|
||||
//
|
||||
// Returns a pointer to the foreign object's data.
|
||||
WREN_API void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size);
|
||||
|
||||
// Stores a new empty list in [slot].
|
||||
WREN_API void wrenSetSlotNewList(WrenVM* vm, int slot);
|
||||
|
||||
// Stores a new empty map in [slot].
|
||||
WREN_API void wrenSetSlotNewMap(WrenVM* vm, int slot);
|
||||
|
||||
// Stores null in [slot].
|
||||
WREN_API void wrenSetSlotNull(WrenVM* vm, int slot);
|
||||
|
||||
// Stores the string [text] in [slot].
|
||||
//
|
||||
// The [text] is copied to a new string within Wren's heap, so you can free
|
||||
// memory used by it after this is called. The length is calculated using
|
||||
// [strlen()]. If the string may contain any null bytes in the middle, then you
|
||||
// should use [wrenSetSlotBytes()] instead.
|
||||
WREN_API void wrenSetSlotString(WrenVM* vm, int slot, const char* text);
|
||||
|
||||
// Stores the value captured in [handle] in [slot].
|
||||
//
|
||||
// This does not release the handle for the value.
|
||||
WREN_API void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle);
|
||||
|
||||
// Returns the number of elements in the list stored in [slot].
|
||||
WREN_API int wrenGetListCount(WrenVM* vm, int slot);
|
||||
|
||||
// Reads element [index] from the list in [listSlot] and stores it in
|
||||
// [elementSlot].
|
||||
WREN_API void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
|
||||
|
||||
// Sets the value stored at [index] in the list at [listSlot],
|
||||
// to the value from [elementSlot].
|
||||
WREN_API void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
|
||||
|
||||
// Takes the value stored at [elementSlot] and inserts it into the list stored
|
||||
// at [listSlot] at [index].
|
||||
//
|
||||
// As in Wren, negative indexes can be used to insert from the end. To append
|
||||
// an element, use `-1` for the index.
|
||||
WREN_API void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot);
|
||||
|
||||
// Returns the number of entries in the map stored in [slot].
|
||||
WREN_API int wrenGetMapCount(WrenVM* vm, int slot);
|
||||
|
||||
// Returns true if the key in [keySlot] is found in the map placed in [mapSlot].
|
||||
WREN_API bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot);
|
||||
|
||||
// Retrieves a value with the key in [keySlot] from the map in [mapSlot] and
|
||||
// stores it in [valueSlot].
|
||||
WREN_API void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot);
|
||||
|
||||
// Takes the value stored at [valueSlot] and inserts it into the map stored
|
||||
// at [mapSlot] with key [keySlot].
|
||||
WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot);
|
||||
|
||||
// Removes a value from the map in [mapSlot], with the key from [keySlot],
|
||||
// and place it in [removedValueSlot]. If not found, [removedValueSlot] is
|
||||
// set to null, the same behaviour as the Wren Map API.
|
||||
WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot,
|
||||
int removedValueSlot);
|
||||
|
||||
// Looks up the top level variable with [name] in resolved [module] and stores
|
||||
// it in [slot].
|
||||
WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
|
||||
int slot);
|
||||
|
||||
// Looks up the top level variable with [name] in resolved [module],
|
||||
// returns false if not found. The module must be imported at the time,
|
||||
// use wrenHasModule to ensure that before calling.
|
||||
WREN_API bool wrenHasVariable(WrenVM* vm, const char* module, const char* name);
|
||||
|
||||
// Returns true if [module] has been imported/resolved before, false if not.
|
||||
WREN_API bool wrenHasModule(WrenVM* vm, const char* module);
|
||||
|
||||
// Sets the current fiber to be aborted, and uses the value in [slot] as the
|
||||
// runtime error object.
|
||||
WREN_API void wrenAbortFiber(WrenVM* vm, int slot);
|
||||
|
||||
// Returns the user data associated with the WrenVM.
|
||||
WREN_API void* wrenGetUserData(WrenVM* vm);
|
||||
|
||||
// Sets user data associated with the WrenVM.
|
||||
WREN_API void wrenSetUserData(WrenVM* vm, void* userData);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user