feat: implement request handling with validation and response formatting

This commit is contained in:
2025-07-29 12:35:38 +00:00
commit a8d61fd475
18 changed files with 55458 additions and 0 deletions
+12
View 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
View 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
View 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;
}
+11573
View File
File diff suppressed because it is too large Load Diff
+206
View 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;
}
File diff suppressed because it is too large Load Diff
+31
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
Executable
BIN
View File
Binary file not shown.
+13497
View File
File diff suppressed because it is too large Load Diff
+554
View 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
Executable
BIN
View File
Binary file not shown.