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