Requests working good.

This commit is contained in:
retoor 2025-07-29 14:35:38 +02:00
commit 9f27915cab
18 changed files with 55458 additions and 0 deletions

12
Makefile Normal file
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
async_http.c Normal file
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
backend.cpp Normal file
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
httplib.h Normal file

File diff suppressed because it is too large Load Diff

206
main.c Normal file
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;
}

27070
merged_source_files.txt Normal file

File diff suppressed because it is too large Load Diff

31
requests.wren Normal file
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
requests_backend.c Normal file
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
requests_example.wren Normal file
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
socket.wren Normal file
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
socket_backend.c Normal file
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
socket_backend.cpp Normal file
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;
}

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
socket_example.wren Normal file
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)

BIN
wren Executable file

Binary file not shown.

13497
wren.c Normal file

File diff suppressed because it is too large Load Diff

554
wren.h Normal file
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

BIN
wren3 Executable file

Binary file not shown.