Start merging things back into a mega header to simplify everything

This commit is contained in:
2026-06-23 23:17:16 +10:00
parent 544669d3cb
commit aeaa497a7b
21 changed files with 18542 additions and 24977 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-4450
View File
File diff suppressed because it is too large Load Diff
-1248
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-130
View File
@@ -1,130 +0,0 @@
#define DN_NET_CURL_CPP
#if defined(_CLANGD)
#define DN_H_WITH_OS 1
#include "../dn.h"
#include "dn_net.h"
#endif
DN_Str8 DN_NET_Str8FromResponseState(DN_NETResponseState state)
{
DN_Str8 result = {};
switch (state) {
case DN_NETResponseState_Nil: result = DN_Str8Lit("Nil"); break;
case DN_NETResponseState_Error: result = DN_Str8Lit("Error"); break;
case DN_NETResponseState_HTTP: result = DN_Str8Lit("HTTP"); break;
case DN_NETResponseState_WSOpen: result = DN_Str8Lit("WS Open"); break;
case DN_NETResponseState_WSText: result = DN_Str8Lit("WS Text"); break;
case DN_NETResponseState_WSBinary: result = DN_Str8Lit("WS Binary"); break;
case DN_NETResponseState_WSClose: result = DN_Str8Lit("WS Close"); break;
case DN_NETResponseState_WSPing: result = DN_Str8Lit("WS Ping"); break;
case DN_NETResponseState_WSPong: result = DN_Str8Lit("WS Pong"); break;
}
return result;
}
DN_NETRequest *DN_NET_RequestFromHandle(DN_NETRequestHandle handle)
{
DN_NETRequest *ptr = DN_Cast(DN_NETRequest *) handle.handle;
DN_NETRequest *result = nullptr;
if (ptr && ptr->gen == handle.gen)
result = ptr;
return result;
}
DN_NETRequestHandle DN_NET_HandleFromRequest(DN_NETRequest *request)
{
DN_NETRequestHandle result = {};
if (request) {
result.handle = DN_Cast(DN_UPtr) request;
result.gen = request->gen;
}
return result;
}
bool DN_NET_ResponseHasFailed(DN_NETResponse const* resp)
{
bool result = false;
if (resp->type == DN_NETRequestType_HTTP)
result = resp->state == DN_NETResponseState_Error || resp->http_status >= 400;
else
result = resp->state == DN_NETResponseState_Error;
return result;
}
DN_Str8 DN_NET_Str8DiagnosticFromResponse(DN_NETResponse const* resp, DN_Arena *arena)
{
DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1);
DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena);
bool resp_failed = DN_NET_ResponseHasFailed(resp);
DN_Str8BuilderAppendF(&builder, "Request %s (%s", resp_failed ? "failed" : "succeeded", resp->type == DN_NETRequestType_HTTP ? "HTTP" : "WS");
if (resp->type == DN_NETRequestType_HTTP) {
if (resp->http_status)
DN_Str8BuilderAppendF(&builder, " %u", resp->http_status);
}
DN_Str8BuilderAppendF(&builder, ")");
if (resp->body.size || resp->error_str8.size) {
DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" with "));
if (resp->body.size)
DN_Str8BuilderAppendF(&builder, "%.*s", DN_Str8PrintFmt(resp->body));
if (resp->error_str8.size)
DN_Str8BuilderAppendF(&builder, "%s%.*s", resp->body.size ? ". " : "", DN_Str8PrintFmt(resp->error_str8));
}
DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena);
DN_TCScratchEnd(&scratch);
return result;
}
void DN_NET_BaseInit(DN_NETCore *net, char *base, DN_U64 base_size)
{
net->base = base;
net->base_size = base_size;
net->mem = DN_MemListFromBuffer(net->base, net->base_size, DN_MemFlags_Nil);
net->arena = DN_ArenaFromMemList(&net->mem);
net->completion_sem = DN_OS_SemaphoreInit(0);
}
DN_NETRequestHandle DN_NET_SetupRequest(DN_NETRequest *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type)
{
// NOTE: Setup request
DN_Assert(request);
if (request) {
if (!request->mem.curr)
request->mem = DN_MemListFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_MemFlags_Nil);
request->arena = DN_ArenaTempBeginFromMemList(&request->mem);
request->type = type;
request->gen = DN_Max(request->gen + 1, 1);
request->url = DN_Str8FromStr8Arena(url, &request->arena);
request->method = DN_Str8FromStr8Arena(DN_Str8TrimWhitespaceAround(method), &request->arena);
if (args) {
request->args.flags = args->flags;
request->args.username = DN_Str8FromStr8Arena(args->username, &request->arena);
request->args.password = DN_Str8FromStr8Arena(args->password, &request->arena);
if (type == DN_NETRequestType_HTTP)
request->args.payload = DN_Str8FromStr8Arena(args->payload, &request->arena);
request->args.headers = DN_ArenaNewArray(&request->arena, DN_Str8, args->headers_size, DN_ZMem_No);
DN_Assert(request->args.headers);
if (request->args.headers) {
for (DN_ForItSize(it, DN_Str8, args->headers, args->headers_size))
request->args.headers[it.index] = DN_Str8FromStr8Arena(*it.data, &request->arena);
request->args.headers_size = args->headers_size;
}
}
request->completion_sem = DN_OS_SemaphoreInit(0);
request->start_response_arena = DN_ArenaTempBeginFromArena(&request->arena);
}
DN_NETRequestHandle result = DN_NET_HandleFromRequest(request);
request->response.request = result;
request->response.type = request->type;
return result;
}
void DN_NET_EndFinishedRequest(DN_NETRequest *request)
{
// NOTE: Deallocate the memory used in the request and reset the string builder
DN_ArenaTempEnd(&request->start_response_arena, DN_ArenaReset_Yes);
}
-134
View File
@@ -1,134 +0,0 @@
#if !defined(DN_NET_H)
#define DN_NET_H
#if defined(_CLANGD)
#define DN_H_WITH_OS 1
#include "../dn.h"
#endif
enum DN_NETRequestType
{
DN_NETRequestType_Nil,
DN_NETRequestType_HTTP,
DN_NETRequestType_WS,
};
enum DN_NETResponseState
{
DN_NETResponseState_Nil,
DN_NETResponseState_Error,
DN_NETResponseState_HTTP,
DN_NETResponseState_WSOpen,
DN_NETResponseState_WSText,
DN_NETResponseState_WSBinary,
DN_NETResponseState_WSClose,
DN_NETResponseState_WSPing,
DN_NETResponseState_WSPong,
};
enum DN_NETWSSend
{
DN_NETWSSend_Text,
DN_NETWSSend_Binary,
DN_NETWSSend_Close,
DN_NETWSSend_Ping,
DN_NETWSSend_Pong,
};
enum DN_NETDoHTTPFlags
{
DN_NETDoHTTPFlags_Nil = 0,
DN_NETDoHTTPFlags_BasicAuth = 1 << 0,
};
struct DN_NETDoHTTPArgs
{
// NOTE: WS and HTTP args
DN_NETDoHTTPFlags flags;
DN_Str8 username;
DN_Str8 password;
DN_Str8 *headers;
DN_U16 headers_size;
// NOTE: HTTP args only
DN_Str8 payload;
};
struct DN_NETRequestHandle
{
DN_UPtr handle;
DN_U64 gen;
};
struct DN_NETResponse
{
// NOTE: Common to WS and HTTP responses
DN_NETRequestType type;
DN_NETResponseState state;
DN_NETRequestHandle request;
DN_Str8 error_str8;
DN_Str8 body;
// NOTE: HTTP responses only
DN_U32 http_status;
};
struct DN_NETRequest
{
DN_MemList mem;
DN_Arena arena;
DN_Arena start_response_arena;
DN_NETRequestType type;
DN_U64 gen;
DN_Str8 url;
DN_Str8 method;
DN_OSSemaphore completion_sem;
DN_NETDoHTTPArgs args;
DN_NETResponse response;
DN_NETRequest *next;
DN_NETRequest *prev;
DN_U64 context[2];
};
typedef void (DN_NETInitFunc) (struct DN_NETCore *net, char *base, DN_U64 base_size);
typedef void (DN_NETDeinitFunc) (struct DN_NETCore *net);
typedef DN_NETRequestHandle(DN_NETDoHTTPFunc) (struct DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args);
typedef DN_NETRequestHandle(DN_NETDoWSFunc) (struct DN_NETCore *net, DN_Str8 url);
typedef void (DN_NETDoWSSendFunc) (DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send);
typedef DN_NETResponse (DN_NETWaitForResponseFunc) (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms);
typedef DN_NETResponse (DN_NETWaitForAnyResponseFunc)(struct DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms);
struct DN_NETInterface
{
DN_NETInitFunc* init;
DN_NETDeinitFunc* deinit;
DN_NETDoHTTPFunc* do_http;
DN_NETDoWSFunc* do_ws;
DN_NETDoWSSendFunc* do_ws_send;
DN_NETWaitForResponseFunc* wait_for_response;
DN_NETWaitForAnyResponseFunc* wait_for_any_response;
};
struct DN_NETCore
{
char *base;
DN_U64 base_size;
DN_MemList mem;
DN_Arena arena;
DN_OSSemaphore completion_sem;
void *context;
DN_NETInterface api;
};
DN_Str8 DN_NET_Str8FromResponseState (DN_NETResponseState state);
DN_NETRequest * DN_NET_RequestFromHandle (DN_NETRequestHandle handle);
DN_NETRequestHandle DN_NET_HandleFromRequest (DN_NETRequest *request);
bool DN_NET_ResponseHasFailed (DN_NETResponse const* resp);
DN_Str8 DN_NET_Str8DiagnosticFromResponse(DN_NETResponse const* resp, DN_Arena *arena);
// NOTE: Internal functions for different networking implementations to use
void DN_NET_BaseInit (DN_NETCore *net, char *base, DN_U64 base_size);
DN_NETRequestHandle DN_NET_SetupRequest (DN_NETRequest *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type);
void DN_NET_EndFinishedRequest (DN_NETRequest *request);
#endif // DN_NET_H
-708
View File
@@ -1,708 +0,0 @@
#define DN_NET_CURL_CPP
#if defined(_CLANGD)
#define DN_H_WITH_OS 1
#include "../dn.h"
#include "dn_net.h"
#include "dn_net_curl.h"
#endif
struct DN_NETCurlRequest
{
void *handle;
struct curl_slist *slist;
char error[CURL_ERROR_SIZE];
bool ws_has_more;
DN_Str8Builder str8_builder;
};
enum DN_NETCurlRingEventType
{
DN_NETCurlRingEventType_Nil,
DN_NETCurlRingEventType_DoRequest,
DN_NETCurlRingEventType_SendWS,
DN_NETCurlRingEventType_ReceivedWSReceipt,
DN_NETCurlRingEventType_DeinitRequest,
};
struct DN_NETCurlRingEvent
{
DN_NETCurlRingEventType type;
DN_NETRequestHandle request;
DN_USize ws_send_size;
DN_NETWSSend ws_send;
};
static DN_NETCurlRequest *DN_NET_CurlRequestFromRequest_(DN_NETRequest *req)
{
DN_NETCurlRequest *result = req ? DN_Cast(DN_NETCurlRequest *) req->context[0] : 0;
return result;
}
static DN_NETCore *DN_NET_CurlNetFromRequest(DN_NETRequest *req)
{
DN_NETCore *result = req ? DN_Cast(DN_NETCore *) req->context[1] : 0;
return result;
}
static bool DN_NET_CurlRequestIsInList(DN_NETRequest const *first, DN_NETRequest const *find)
{
bool result = false;
for (DN_NETRequest const *it = first; !result && it; it = it->next)
result = find == it;
return result;
}
static void DN_NET_CurlMarkRequestDone_(DN_NETCore *net, DN_NETRequest *request)
{
DN_Assert(request);
DN_Assert(net);
// NOTE: The done list in CURL is also used as a place to put websocket requests after removing it
// from the 'ws_list'. By doing this we are stopping the CURL thread from receiving more data on
// the socket as that thread ticks the list of 'ws_list' sockets for data.
//
// Once the caller waited and has received the data from the websocket, the request is put back
// into the 'ws_list' which then lets the CURL thread start receiving more data for that socket.
//
// Since CURL uses a background thread, we do this behind a mutex
DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *)net->context;
for (DN_OS_MutexScope(&curl->list_mutex)) {
DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, request));
DN_DoublyLLDetach(curl->thread_request_list, request);
DN_DoublyLLAppend(curl->response_list, request);
}
DN_OS_SemaphoreIncrement(&net->completion_sem, 1);
DN_OS_SemaphoreIncrement(&request->completion_sem, 1);
}
static DN_USize DN_NET_CurlHTTPCallback_(char *payload, DN_USize size, DN_USize count, void *user_data)
{
DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data;
DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req);
DN_USize result = 0;
DN_USize payload_size = size * count;
if (DN_Str8BuilderAppendBytesCopy(&curl_req->str8_builder, payload, payload_size))
result = payload_size;
return result;
}
static int32_t DN_NET_CurlThreadEntryPoint_(DN_OSThread *thread)
{
DN_NETCore *net = DN_Cast(DN_NETCore *) thread->user_context;
DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context;
DN_OS_ThreadSetNameFmt("%.*s", DN_Str8PrintFmt(curl->thread.name));
while (!curl->kill_thread) {
DN_TCScratch tmem = DN_TCScratchBeginArena(nullptr, 0);
// NOTE: Handle events sitting in the ring queue
for (bool dequeue_ring = true; dequeue_ring;) {
DN_NETCurlRingEvent event = {};
for (DN_OS_MutexScope(&curl->ring_mutex)) {
if (DN_RingHasData(&curl->ring, sizeof(event)))
DN_RingRead(&curl->ring, &event, sizeof(event));
}
DN_NETRequest *req = DN_NET_RequestFromHandle(event.request);
DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req);
switch (event.type) {
case DN_NETCurlRingEventType_Nil: dequeue_ring = false; break;
case DN_NETCurlRingEventType_DoRequest: {
DN_Assert(req->response.state == DN_NETResponseState_Nil);
DN_Assert(req->type != DN_NETRequestType_Nil);
// NOTE: Attach it to the CURL thread's request list
for (DN_OS_MutexScope(&curl->list_mutex)) {
DN_Assert(DN_NET_CurlRequestIsInList(curl->request_list, req));
DN_DoublyLLDetach(curl->request_list, req);
}
DN_DoublyLLAppend(curl->thread_request_list, req);
// NOTE: Add the connection to CURLM and start ticking it once we finish handling all the
// ring events
CURLMcode multi_add = curl_multi_add_handle(curl->thread_curlm, curl_req->handle);
DN_Assert(multi_add == CURLM_OK);
} break;
case DN_NETCurlRingEventType_SendWS: {
DN_Str8 payload = {};
for (DN_OS_MutexScope(&curl->ring_mutex)) {
DN_Assert(DN_RingHasData(&curl->ring, event.ws_send_size));
payload = DN_Str8AllocArena(event.ws_send_size, DN_ZMem_No, &tmem.arena);
DN_RingRead(&curl->ring, payload.data, payload.size);
}
DN_U32 curlws_flag = 0;
switch (event.ws_send) {
case DN_NETWSSend_Text: curlws_flag = CURLWS_TEXT; break;
case DN_NETWSSend_Binary: curlws_flag = CURLWS_BINARY; break;
case DN_NETWSSend_Close: curlws_flag = CURLWS_CLOSE; break;
case DN_NETWSSend_Ping: curlws_flag = CURLWS_PING; break;
case DN_NETWSSend_Pong: curlws_flag = CURLWS_PONG; break;
}
DN_Assert(req->type == DN_NETRequestType_WS);
DN_Assert(req->response.state >= DN_NETResponseState_WSOpen && req->response.state <= DN_NETResponseState_WSPong);
DN_USize sent = 0;
CURLcode send_result = curl_ws_send(curl_req->handle, payload.data, payload.size, &sent, 0, curlws_flag);
DN_AssertF(send_result == CURLE_OK, "Failed to send: %s", curl_easy_strerror(send_result));
DN_AssertF(sent == payload.size, "Failed to send all bytes (%zu vs %zu)", sent, payload.size);
} break;
case DN_NETCurlRingEventType_ReceivedWSReceipt: {
DN_Assert(req->type == DN_NETRequestType_WS);
DN_Assert(req->response.state >= DN_NETResponseState_WSOpen && req->response.state <= DN_NETResponseState_WSPong);
req->response.state = DN_NETResponseState_WSOpen;
// NOTE: End the temp memory storing the WS data we just read and the user returned to us
// (we got their receipt back). Then restart the temp memory scope for the next websocket
// payload
DN_NET_EndFinishedRequest(req);
req->start_response_arena = DN_ArenaTempBeginFromArena(&req->arena);
curl_req->str8_builder = DN_Str8BuilderFromArena(&req->start_response_arena);
for (DN_OS_MutexScope(&curl->list_mutex)) {
DN_Assert(DN_NET_CurlRequestIsInList(curl->request_list, req));
DN_DoublyLLDetach(curl->request_list, req);
}
DN_DoublyLLAppend(curl->thread_request_list, req);
} break;
case DN_NETCurlRingEventType_DeinitRequest: {
DN_Assert(event.request.handle != 0);
DN_NETRequest *request = DN_Cast(DN_NETRequest *) event.request.handle;
// NOTE: Detach the request from the deinit list. This brings the request into this
// thread's provenance, no other threads modifying the deinit list will race with us.
for (DN_OS_MutexScope(&curl->list_mutex)) {
DN_Assert(DN_NET_CurlRequestIsInList(curl->deinit_list, request));
DN_DoublyLLDetach(curl->deinit_list, request);
}
// NOTE: Now we can modify the request, release resources
DN_NET_EndFinishedRequest(request);
DN_OS_SemaphoreDeinit(&request->completion_sem);
curl_multi_remove_handle(curl->thread_curlm, curl_req->handle);
curl_slist_free_all(curl_req->slist);
curl_easy_reset(curl_req->handle);
CURL *copy = curl_req->handle;
*curl_req = {};
curl_req->handle = copy;
// NOTE: Zero the struct preserving just the data we need to retain
DN_NETRequest resetter = {};
resetter.arena = request->arena;
resetter.gen = request->gen;
DN_Memcpy(resetter.context, request->context, sizeof(resetter.context));
*request = resetter;
// NOTE: Add it to the free list
for (DN_OS_MutexScope(&curl->list_mutex))
DN_DoublyLLAppend(curl->free_list, request);
} break;
}
}
// NOTE: Pump handles
int running_handles = 0;
CURLMcode perform_result = curl_multi_perform(curl->thread_curlm, &running_handles);
if (perform_result != CURLM_OK)
DN_AssertInvalidCodePath;
// NOTE: Check pump result
for (;;) {
int msgs_in_queue = 0;
CURLMsg *msg = curl_multi_info_read(curl->thread_curlm, &msgs_in_queue);
if (msg) {
// NOTE: Get request handle
DN_NETRequest *req = nullptr;
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, DN_Cast(void **) & req);
DN_Assert(req);
DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, req));
DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req);
DN_Assert(curl_req->handle == msg->easy_handle);
if (msg->data.result == CURLE_OK) {
// NOTE: Get HTTP response code
CURLcode get_result = curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &req->response.http_status);
if (get_result == CURLE_OK) {
if (req->type == DN_NETRequestType_HTTP) {
req->response.state = DN_NETResponseState_HTTP;
} else {
DN_Assert(req->type == DN_NETRequestType_WS);
req->response.state = DN_NETResponseState_WSOpen;
}
} else {
req->response.error_str8 = DN_Str8FromFmtArena(&req->start_response_arena, "Failed to get HTTP response status (CURL %d): %s", msg->data.result, curl_easy_strerror(get_result));
req->response.state = DN_NETResponseState_Error;
}
} else {
DN_USize curl_extended_error_size = DN_CStr8Size(curl_req->error);
req->response.state = DN_NETResponseState_Error;
req->response.error_str8 = DN_Str8FromFmtArena(&req->start_response_arena,
"HTTP request '%.*s' failed (CURL %d): %s%s%s%s",
DN_Str8PrintFmt(req->url),
msg->data.result,
curl_easy_strerror(msg->data.result),
curl_extended_error_size ? " (" : "",
curl_extended_error_size ? curl_req->error : "",
curl_extended_error_size ? ")" : "");
}
if (req->type == DN_NETRequestType_HTTP || req->response.state == DN_NETResponseState_Error) {
// NOTE: Remove the request from the multi handle if we're a HTTP request
// because it typically terminates the connection. In websockets the
// connection remains in the multi-handle to allow you to send and
// receive WS data from it.
//
// If there's an error (either websocket or HTTP) we will also remove the
// connection from the multi handle as it failed. One a connection has
// failed, curl will not poll that connection so there's no point keeping
// it attached to the multi handle.
curl_multi_remove_handle(curl->thread_curlm, msg->easy_handle);
}
DN_NET_CurlMarkRequestDone_(net, req);
}
if (msgs_in_queue == 0)
break;
}
// NOTE: Check websockets
DN_USize ws_count = 0;
for (DN_NETRequest *req = curl->thread_request_list; req; req = req->next) {
DN_Assert(req->type == DN_NETRequestType_WS || req->type == DN_NETRequestType_HTTP);
if (req->type != DN_NETRequestType_WS || !(req->response.state >= DN_NETResponseState_WSOpen && req->response.state <= DN_NETResponseState_WSPong))
continue;
ws_count++;
const curl_ws_frame *meta = nullptr;
DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req);
CURLcode receive_result = CURLE_OK;
while (receive_result == CURLE_OK) {
// NOTE: Determine WS payload size received. Note that since we pass in a null pointer CURL
// will set meta->len to 0 and say that there's meta->bytesleft in the next chunk.
DN_USize bytes_read = 0;
receive_result = curl_ws_recv(curl_req->handle, nullptr, 0, &bytes_read, &meta);
if (receive_result != CURLE_OK)
continue;
DN_Assert(meta->len == 0);
if (meta->flags & CURLWS_TEXT)
req->response.state = DN_NETResponseState_WSText;
if (meta->flags & CURLWS_BINARY)
req->response.state = DN_NETResponseState_WSBinary;
if (meta->flags & CURLWS_PING)
req->response.state = DN_NETResponseState_WSPing;
if (meta->flags & CURLWS_PONG)
req->response.state = DN_NETResponseState_WSPong;
if (meta->flags & CURLWS_CLOSE)
req->response.state = DN_NETResponseState_WSClose;
curl_req->ws_has_more = meta->flags & CURLWS_CONT;
if (curl_req->ws_has_more) {
bool is_text_or_binary = req->response.state == DN_NETResponseState_WSText ||
req->response.state == DN_NETResponseState_WSBinary;
DN_Assert(is_text_or_binary);
}
// NOTE: Allocate and read (we use meta->bytesleft as per comment from initial recv)
if (meta->bytesleft) {
DN_Str8 buffer = DN_Str8AllocArena(meta->bytesleft, DN_ZMem_No, &req->start_response_arena);
DN_Assert(buffer.size == DN_Cast(DN_USize)meta->bytesleft);
receive_result = curl_ws_recv(curl_req->handle, buffer.data, buffer.size, &buffer.size, &meta);
DN_Assert(buffer.size == DN_Cast(DN_USize)meta->len);
DN_Str8BuilderAppendRef(&curl_req->str8_builder, buffer);
}
// NOTE: There are more bytes coming if meta->bytesleft is set, (e.g. the next chunk. We
// just read the current chunk).
//
// > If this is not a complete fragment, the bytesleft field informs about how many
// additional bytes are expected to arrive before this fragment is complete.
curl_req->ws_has_more |= meta && meta->bytesleft > 0;
if (!curl_req->ws_has_more)
break;
}
// NOTE: curl_ws_recv returns CURLE_GOT_NOTHING if the associated connection is closed.
if (receive_result == CURLE_GOT_NOTHING)
curl_req->ws_has_more = false;
// NOTE: We read all the possible bytes that CURL has received for this message, but, there are
// more bytes left that we will receive on subsequent calls. We will continue to the next
// request and return back to this one when PumpRequests is called again where hopefully that
// data has arrived.
if (curl_req->ws_has_more)
continue;
// For CURLE_AGAIN
//
// > Instead of blocking, the function returns CURLE_AGAIN. The correct behavior is then to
// > wait for the socket to signal readability before calling this function again.
//
// In which case we continue ticking the other sockets and eventually exit once all ticked.
// Right after this we wait on the CURLM instance which will wake us up again when there's
// data to be read.
//
// if we received data, e.g. state was set to Text, Binary ... e.t.c we bypass this and
// report it to the user first. When the user waits for the response, they consume the data
// and then that will reinsert it into request list for CURL to read from the socket again.
bool received_data = (req->response.state >= DN_NETResponseState_WSText && req->response.state <= DN_NETResponseState_WSPong);
if (receive_result == CURLE_AGAIN && !received_data)
continue;
if (!received_data) {
if (receive_result == CURLE_GOT_NOTHING) {
req->response.state = DN_NETResponseState_WSClose;
} else if (receive_result != CURLE_OK) {
DN_USize curl_extended_error_size = DN_CStr8Size(curl_req->error);
req->response.state = DN_NETResponseState_Error;
req->response.error_str8 = DN_Str8FromFmtArena(&req->start_response_arena,
"Websocket receive '%.*s' failed (CURL %d): %s%s%s%s",
DN_Str8PrintFmt(req->url),
receive_result,
curl_easy_strerror(receive_result),
curl_extended_error_size ? " (" : "",
curl_extended_error_size ? curl_req->error : "",
curl_extended_error_size ? ")" : "");
}
}
DN_NETRequest *request_copy = req;
req = req->prev;
DN_NET_CurlMarkRequestDone_(net, request_copy);
if (!req)
break;
}
DN_I32 sleep_time_ms = ws_count > 0 ? 16 : INT32_MAX;
curl_multi_poll(curl->thread_curlm, nullptr, 0, sleep_time_ms, nullptr);
DN_TCScratchEnd(&tmem);
}
return 0;
}
DN_NETInterface DN_NET_CurlInterface()
{
DN_NETInterface result = {};
result.init = DN_NET_CurlInit;
result.deinit = DN_NET_CurlDeinit;
result.do_http = DN_NET_CurlDoHTTP;
result.do_ws = DN_NET_CurlDoWS;
result.do_ws_send = DN_NET_CurlDoWSSend;
result.wait_for_response = DN_NET_CurlWaitForResponse;
result.wait_for_any_response = DN_NET_CurlWaitForAnyResponse;
return result;
}
void DN_NET_CurlInit(DN_NETCore *net, char *base, DN_U64 base_size)
{
DN_NET_BaseInit(net, base, base_size);
DN_NETCurlCore *curl = DN_ArenaNew(&net->arena, DN_NETCurlCore, DN_ZMem_Yes);
net->context = curl;
net->api = DN_NET_CurlInterface();
DN_USize arena_bytes_avail = (net->arena.mem->curr->reserve - net->arena.mem->curr->used);
curl->ring.size = arena_bytes_avail / 2;
curl->ring.base = DN_Cast(char *) DN_ArenaAlloc(&net->arena, curl->ring.size, /*align*/ 1, DN_ZMem_Yes);
DN_Assert(curl->ring.base);
curl->ring_mutex = DN_OS_MutexInit();
curl->list_mutex = DN_OS_MutexInit();
curl->thread_curlm = DN_Cast(CURLM *) curl_multi_init();
DN_FmtAppend(curl->thread.name.data, &curl->thread.name.size, sizeof(curl->thread.name.data), "NET (CURL)");
DN_OS_ThreadInit(&curl->thread, DN_NET_CurlThreadEntryPoint_, nullptr, DN_TCInitArgsDefault(), net);
}
void DN_NET_CurlDeinit(DN_NETCore *net)
{
DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context;
curl->kill_thread = true;
curl_multi_wakeup(curl->thread_curlm);
DN_OS_ThreadJoin(&curl->thread, DN_TCDeinitArenas_Yes);
}
static DN_NETRequestHandle DN_NET_CurlDoRequest_(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type)
{
// NOTE: Allocate the request
DN_NETCurlCore *curl_core = DN_Cast(DN_NETCurlCore *) net->context;
DN_NETRequest *req = nullptr;
DN_NETRequestHandle result = {};
{
// NOTE: The free list is modified by both the calling thread and the CURLM thread (which ticks
// all the requests in the background for us)
for (DN_OS_MutexScope(&curl_core->list_mutex)) {
req = curl_core->free_list;
DN_DoublyLLDetach(curl_core->free_list, req);
}
// NOTE None in the free list so allocate one
if (!req) {
DN_OS_MutexLock(&curl_core->list_mutex);
DN_U64 arena_pos = DN_MemListPos(net->arena.mem);
req = DN_ArenaNewZ(&net->arena, DN_NETRequest);
DN_NETCurlRequest *curl_req = DN_ArenaNewZ(&net->arena, DN_NETCurlRequest);
if (!req || !curl_req) {
DN_MemListPopTo(net->arena.mem, arena_pos);
DN_OS_MutexUnlock(&curl_core->list_mutex);
return result;
}
DN_OS_MutexUnlock(&curl_core->list_mutex);
curl_req->handle = DN_Cast(CURL *) curl_easy_init();
req->context[0] = DN_Cast(DN_UPtr) curl_req;
}
}
// NOTE: Setup the request
DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req);
{
result = DN_NET_SetupRequest(req, url, method, args, type);
req->context[1] = DN_Cast(DN_UPtr) net;
curl_req->str8_builder = DN_Str8BuilderFromArena(&req->start_response_arena);
}
// NOTE: Setup the request for curl API
{
CURL *curl = curl_req->handle;
curl_easy_setopt(curl, CURLOPT_PRIVATE, req);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_req->error);
// NOTE: Perform request and read all response headers before handing
// control back to app.
curl_easy_setopt(curl, CURLOPT_URL, req->url.data);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
// NOTE: Setup response handler
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DN_NET_CurlHTTPCallback_);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, req);
// NOTE: Assign HTTP headers
for (DN_ForItSize(it, DN_Str8, req->args.headers, req->args.headers_size)) {
DN_Assert(it.data->data[it.data->size] == 0);
curl_req->slist = curl_slist_append(curl_req->slist, it.data->data);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_req->slist);
// NOTE: Setup handle for protocol
switch (req->type) {
case DN_NETRequestType_Nil: DN_AssertInvalidCodePath; break;
case DN_NETRequestType_WS: {
curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L);
} break;
case DN_NETRequestType_HTTP: {
DN_Str8 const GET = DN_Str8Lit("GET");
DN_Str8 const POST = DN_Str8Lit("POST");
if (DN_Str8EqInsensitive(req->method, GET)) {
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
} else if (DN_Str8EqInsensitive(req->method, POST)) {
curl_easy_setopt(curl, CURLOPT_POST, 1);
if (req->args.payload.size > DN_Gigabytes(2))
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, req->args.payload.size);
else
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, req->args.payload.size);
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, req->args.payload.data);
} else {
DN_AssertInvalidCodePathF("Unimplemented");
}
} break;
}
// NOTE: Handle basic auth
if (req->args.flags & DN_NETDoHTTPFlags_BasicAuth) {
if (req->args.username.size && req->args.password.size) {
DN_Assert(req->args.username.data[req->args.username.size] == 0);
DN_Assert(req->args.password.data[req->args.password.size] == 0);
curl_easy_setopt(curl, CURLOPT_USERNAME, req->args.username.data);
curl_easy_setopt(curl, CURLOPT_PASSWORD, req->args.password.data);
}
}
}
// NOTE: Dispatch the request to the CURL thread
{
// NOTE: Immediately add the request to the request list so it happens "atomically" in the
// calling thread. If the calling thread deinitialises this layer before the CURL thread can be
// pre-empted, we can lose track of this request.
for (DN_OS_MutexScope(&curl_core->list_mutex))
DN_DoublyLLAppend(curl_core->request_list, req);
// NOTE: Enqueue request to go into CURL's ring queue. The CURL thread will sleep and wait for
// bytes to come in for the request and then dump the response into the done list to be consumed
// via wait for response
DN_NETCurlRingEvent event = {};
event.type = DN_NETCurlRingEventType_DoRequest;
event.request = result;
for (DN_OS_MutexScope(&curl_core->ring_mutex))
DN_RingWriteStruct(&curl_core->ring, &event);
curl_multi_wakeup(curl_core->thread_curlm);
}
return result;
}
DN_NETRequestHandle DN_NET_CurlDoHTTP(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args)
{
DN_NETRequestHandle result = DN_NET_CurlDoRequest_(net, url, method, args, DN_NETRequestType_HTTP);
return result;
}
DN_NETRequestHandle DN_NET_CurlDoWSArgs(DN_NETCore *net, DN_Str8 url, DN_NETDoHTTPArgs const *args)
{
DN_NETRequestHandle result = DN_NET_CurlDoRequest_(net, url, DN_Str8Lit(""), args, DN_NETRequestType_WS);
return result;
}
DN_NETRequestHandle DN_NET_CurlDoWS(DN_NETCore *net, DN_Str8 url)
{
DN_NETRequestHandle result = DN_NET_CurlDoWSArgs(net, url, nullptr);
return result;
}
void DN_NET_CurlDoWSSend(DN_NETRequestHandle handle, DN_Str8 payload, DN_NETWSSend send)
{
DN_NETRequest *req = DN_NET_RequestFromHandle(handle);
if (!req)
return;
DN_NETCore *net = DN_NET_CurlNetFromRequest(req);
DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context;
DN_Assert(curl);
DN_NETCurlRingEvent event = {};
event.type = DN_NETCurlRingEventType_SendWS;
event.request = handle;
event.ws_send_size = payload.size;
event.ws_send = send;
for (DN_OS_MutexScope(&curl->ring_mutex)) {
DN_Assert(DN_RingHasSpace(&curl->ring, payload.size));
DN_RingWriteStruct(&curl->ring, &event);
DN_RingWrite(&curl->ring, payload.data, payload.size);
}
curl_multi_wakeup(curl->thread_curlm);
}
static DN_NETResponse DN_NET_CurlHandleFinishedRequest_(DN_NETCurlCore *curl, DN_NETRequest *req, DN_Arena *arena)
{
// NOTE: Generate the response, copy out the strings into the user given memory
DN_NETResponse result = req->response;
DN_NETCurlRequest *curl_req = DN_NET_CurlRequestFromRequest_(req);
{
result.body = DN_Str8FromStr8BuilderArena(&curl_req->str8_builder, arena);
if (result.error_str8.size)
result.error_str8 = DN_Str8FromStr8Arena(result.error_str8, arena);
}
bool continue_ws_request = false;
if (req->type == DN_NETRequestType_WS &&
req->response.state != DN_NETResponseState_Error &&
req->response.state != DN_NETResponseState_WSClose) {
continue_ws_request = true;
}
// NOTE: Put the request into the requisite list
for (DN_OS_MutexScope(&curl->list_mutex)) {
// NOTE: Dequeue the request, it _must_ have been in the response list at this point for it to
// have ben waitable in the first place.
DN_AssertF(DN_NET_CurlRequestIsInList(curl->response_list, req),
"A completed response should only signal the completion semaphore when it's in the response list");
DN_DoublyLLDetach(curl->response_list, req);
// NOTE: A websocket that is continuing to get data should go back into the request list because
// there's more data to be received. All other requests need to go into the deinit list (so that
// we keep track of it in the time inbetween it takes for the CURL thread to be scheduled and
// release the CURL handle from CURLM and release resources e.t.c.)
if (continue_ws_request)
DN_DoublyLLAppend(curl->request_list, req);
else
DN_DoublyLLAppend(curl->deinit_list, req);
}
// NOTE: Submit the post-request event to the CURL thread
DN_NETCurlRingEvent event = {};
event.request = DN_NET_HandleFromRequest(req);
if (continue_ws_request) {
event.type = DN_NETCurlRingEventType_ReceivedWSReceipt;
} else {
// NOTE: Deinit _has_ to be sent to the CURL thread because we need to remove the CURL handle
// from the CURLM instance and the CURL thread uses the CURLM instance (e.g. CURLM is not thread
// safe)
event.type = DN_NETCurlRingEventType_DeinitRequest;
}
for (DN_OS_MutexScope(&curl->ring_mutex))
DN_RingWriteStruct(&curl->ring, &event);
curl_multi_wakeup(curl->thread_curlm);
return result;
}
DN_NETResponse DN_NET_CurlWaitForResponse(DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms)
{
DN_NETResponse result = {};
DN_NETRequest *req = DN_NET_RequestFromHandle(handle);
if (!req)
return result;
DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[1];
DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context;
DN_Assert(curl);
DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&req->completion_sem, timeout_ms);
if (wait != DN_OSSemaphoreWaitResult_Success)
return result;
// NOTE: Decrement the global 'request done' completion semaphore since the user consumed the
// request individually.
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/);
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
// NOTE: Finish handling the response
result = DN_NET_CurlHandleFinishedRequest_(curl, req, arena);
return result;
}
DN_NETResponse DN_NET_CurlWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms)
{
DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context;
DN_Assert(curl);
DN_NETResponse result = {};
DN_OSSemaphoreWaitResult req_wait = DN_OS_SemaphoreWait(&net->completion_sem, timeout_ms);
if (req_wait != DN_OSSemaphoreWaitResult_Success)
return result;
// NOTE: Just grab the handle, handle finished request will dequeue for us
DN_NETRequestHandle handle = {};
for (DN_OS_MutexScope(&curl->list_mutex)) {
DN_Assert(curl->response_list);
handle = DN_NET_HandleFromRequest(curl->response_list);
}
// NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore
DN_NETRequest *req = DN_NET_RequestFromHandle(handle);
DN_OSSemaphoreWaitResult net_wait = DN_OS_SemaphoreWait(&req->completion_sem, 0 /*timeout_ms*/);
DN_AssertF(net_wait == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait);
// NOTE: Finish handling the response
result = DN_NET_CurlHandleFinishedRequest_(curl, req, arena);
return result;
}
-39
View File
@@ -1,39 +0,0 @@
#if !defined(DN_NET_CURL_H)
#define DN_NET_CURL_H
#if defined(_CLANGD)
#include "dn_net.h"
#endif
struct DN_NETCurlCore
{
// NOTE: Shared w/ user and networking thread
DN_Ring ring;
DN_OSMutex ring_mutex;
bool kill_thread;
DN_OSMutex list_mutex; // Lock for request, response, deinit, free list
DN_NETRequest *request_list; // Current requests submitted by the user thread awaiting to move into the thread request list
DN_NETRequest *response_list; // Finished requests that are to be deqeued by the user via wait for response
DN_NETRequest *deinit_list; // Requests that are finished and are awaiting to be de-initialised by the CURL thread
DN_NETRequest *free_list; // Request pool that new requests will use before allocating
// NOTE: Networking thread only
DN_NETRequest *thread_request_list; // Current requests being executed by the CURL thread.
// This list is exclusively owned by the CURL thread so no locking is needed
DN_OSThread thread;
void *thread_curlm;
};
#define DN_NET_CurlCoreFromNet(net) ((net) ? (DN_Cast(DN_NETCurlCore *)(net)->context) : nullptr);
DN_NETInterface DN_NET_CurlInterface ();
void DN_NET_CurlInit (DN_NETCore *net, char *base, DN_U64 base_size);
void DN_NET_CurlDeinit (DN_NETCore *net);
DN_NETRequestHandle DN_NET_CurlDoHTTP (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args);
DN_NETRequestHandle DN_NET_CurlDoWSArgs (DN_NETCore *net, DN_Str8 url, DN_NETDoHTTPArgs const *args);
DN_NETRequestHandle DN_NET_CurlDoWS (DN_NETCore *net, DN_Str8 url);
void DN_NET_CurlDoWSSend (DN_NETRequestHandle handle, DN_Str8 payload, DN_NETWSSend send);
DN_NETResponse DN_NET_CurlWaitForResponse (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms);
DN_NETResponse DN_NET_CurlWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms);
#endif // !defined(DN_NET_CURL_H)
-467
View File
@@ -1,467 +0,0 @@
#if !defined(__EMSCRIPTEN__)
#error "This file can only be compiled with Emscripten"
#endif
#include <emscripten.h>
#include <emscripten/fetch.h>
#include <emscripten/websocket.h>
#if defined(_CLANGD)
#include "dn_net.h"
#include "dn_net_emscripten.h"
#endif
struct DN_NETEmcWSEvent
{
DN_NETResponseState state;
DN_Str8 payload;
DN_NETEmcWSEvent *next;
};
struct DN_NETEmcCore
{
DN_Pool pool;
DN_NETRequest *response_list; // Responses received that are to be deqeued via wait for response
DN_NETRequest *free_list; // Request pool that new requests will use before allocating
};
struct DN_NETEmcRequest
{
int socket;
DN_NETEmcWSEvent *first_event;
DN_NETEmcWSEvent *last_event;
};
DN_NETInterface DN_NET_EmcInterface()
{
DN_NETInterface result = {};
result.init = DN_NET_EmcInit;
result.deinit = DN_NET_EmcDeinit;
result.do_http = DN_NET_EmcDoHTTP;
result.do_ws = DN_NET_EmcDoWS;
result.do_ws_send = DN_NET_EmcDoWSSend;
result.wait_for_response = DN_NET_EmcWaitForResponse;
result.wait_for_any_response = DN_NET_EmcWaitForAnyResponse;
return result;
}
static DN_NETEmcWSEvent *DN_NET_EmcAllocWSEvent_(DN_NETRequest *request)
{
// NOTE: Allocate the event and attach to the request
DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request->context[1];
DN_NETEmcWSEvent *result = DN_ArenaNew(&request->arena, DN_NETEmcWSEvent, DN_ZMem_Yes);
DN_Assert(result);
if (result) {
if (!emc_request->first_event)
emc_request->first_event = result;
if (emc_request->last_event)
emc_request->last_event->next = result;
emc_request->last_event = result;
}
return result;
}
static void DN_NET_EmcOnRequestDone_(DN_NETCore *net, DN_NETRequest *request)
{
// NOTE: This may be call multiple times on the same request if we get multiple responses when we
// yield to the javascript event loop, e.g. the application received multiple WS payloads before
// it waited and consequently consumed the response from the payload.
//
// So if the next pointer is already set, then it should be that the request is already enqueued.
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
if (!request->next && !request->prev && request != emc->response_list) {
request->prev = nullptr;
request->next = emc->response_list;
if (emc->response_list)
emc->response_list->prev = request;
emc->response_list = request;
}
DN_OS_SemaphoreIncrement(&net->completion_sem, 1);
DN_OS_SemaphoreIncrement(&request->completion_sem, 1);
}
static bool DN_NET_EmcWSOnOpen(int eventType, EmscriptenWebSocketOpenEvent const *event, void *user_data)
{
DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data;
DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0];
DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req);
net_event->state = DN_NETResponseState_WSOpen;
DN_NET_EmcOnRequestDone_(net, req);
return true;
}
static bool DN_NET_EmcWSOnMessage(int eventType, const EmscriptenWebSocketMessageEvent *event, void *user_data)
{
DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data;
DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0];
DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req);
net_event->state = event->isText ? DN_NETResponseState_WSText : DN_NETResponseState_WSBinary;
if (event->numBytes > 0)
net_event->payload = DN_Str8FromPtrArena(event->data, event->numBytes, &req->arena);
DN_NET_EmcOnRequestDone_(net, req);
return true;
}
static bool DN_NET_EmcWSOnError(int eventType, EmscriptenWebSocketErrorEvent const *event, void *user_data)
{
DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data;
DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0];
DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req);
net_event->state = DN_NETResponseState_Error;
DN_NET_EmcOnRequestDone_(net, req);
return true;
}
static bool DN_NET_EmcWSOnClose(int eventType, EmscriptenWebSocketCloseEvent const *event, void *user_data)
{
DN_NETRequest *req = DN_Cast(DN_NETRequest *) user_data;
DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0];
DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(req);
net_event->state = DN_NETResponseState_WSClose;
net_event->payload = DN_Str8FromFmtArena(&req->arena, "Websocket closed '%.*s': (%u) %s (was %s close)", DN_Str8PrintFmt(req->url), event->code, event->reason, event->wasClean ? "clean" : "unclean");
DN_NET_EmcOnRequestDone_(net, req);
return true;
}
static void DN_NET_EmcHTTPSuccessCallback(emscripten_fetch_t *fetch)
{
DN_NETRequest *req = DN_Cast(DN_NETRequest *) fetch->userData;
DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0];
req->response.http_status = fetch->status;
req->response.state = DN_NETResponseState_HTTP;
req->response.body = DN_Str8FromStr8Arena(DN_Str8FromPtr(fetch->data, fetch->numBytes - 1), &req->arena);
DN_NET_EmcOnRequestDone_(net, req);
}
static void DN_NET_EmcHTTPFailCallback(emscripten_fetch_t *fetch)
{
DN_NETRequest *req = DN_Cast(DN_NETRequest *) fetch->userData;
DN_NETCore *net = DN_Cast(DN_NETCore *) req->context[0];
req->response.http_status = fetch->status;
req->response.state = DN_NETResponseState_Error;
DN_NET_EmcOnRequestDone_(net, req);
}
static void DN_NET_EmcHTTPProgressCallback(emscripten_fetch_t *fetch)
{
}
void DN_NET_EmcInit(DN_NETCore *net, char *base, DN_U64 base_size)
{
DN_NET_BaseInit(net, base, base_size);
DN_NETEmcCore *emc = DN_ArenaNew(&net->arena, DN_NETEmcCore, DN_ZMem_Yes);
emc->pool = DN_PoolFromArena(&net->arena, 0);
net->context = emc;
}
void DN_NET_EmcDeinit(DN_NETCore *net)
{
(void)net;
// TODO: Track all the request handles and clean it up
}
static DN_NETRequest *DN_NET_EmcAllocRequest_(DN_NETCore *net)
{
// NOTE: Allocate request
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
DN_NETRequest *result = emc->free_list;
if (result) {
emc->free_list = emc->free_list->next;
result->next = nullptr;
DN_Assert(result->prev == nullptr);
if (emc->free_list) {
DN_Assert(emc->free_list->prev == nullptr);
}
} else {
// NOTE: Setup the request's arena here. WASM doesn't have the concept of virtual memory
// so we use malloc to initialise it.
result = DN_ArenaNew(&net->arena, DN_NETRequest, DN_ZMem_Yes);
if (result) {
result->arena = DN_ArenaFromMemList(&result->mem);
}
}
// NOTE: Setup some emscripten specific data into our request context
if (result) {
result->context[0] = DN_Cast(DN_UPtr) net;
}
return result;
}
DN_NETRequestHandle DN_NET_EmcDoHTTP(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args)
{
DN_NETRequest *req = DN_NET_EmcAllocRequest_(net);
DN_NETRequestHandle result = DN_NET_SetupRequest(req, url, method, args, DN_NETRequestType_HTTP);
// NOTE: Setup the HTTP request via Emscripten
emscripten_fetch_attr_t fetch_attribs = {};
{
DN_Assert(req->args.payload.data[req->args.payload.size] == 0);
DN_Assert(req->url.data[req->url.size] == 0);
// NOTE: Setup request for emscripten
emscripten_fetch_attr_init(&fetch_attribs);
fetch_attribs.requestData = req->args.payload.data;
fetch_attribs.requestDataSize = req->args.payload.size;
DN_Assert(req->method.size < DN_ArrayCountU(fetch_attribs.requestMethod));
DN_Memcpy(fetch_attribs.requestMethod, req->method.data, req->method.size);
fetch_attribs.requestMethod[req->method.size] = 0;
// NOTE: Assign HTTP headers
if (req->args.headers_size) {
char **headers = DN_ArenaNewArray(&req->start_response_arena, char *, req->args.headers_size + 1, DN_ZMem_Yes);
for (DN_ForItSize(it, DN_Str8, req->args.headers, req->args.headers_size)) {
DN_Assert(it.data->data[it.data->size] == 0);
headers[it.index] = it.data->data;
}
fetch_attribs.requestHeaders = headers;
}
// NOTE: Handle basic auth
if (req->args.flags & DN_NETDoHTTPFlags_BasicAuth) {
if (req->args.username.size && req->args.password.size) {
DN_Assert(req->args.username.data[req->args.username.size] == 0);
DN_Assert(req->args.password.data[req->args.password.size] == 0);
fetch_attribs.withCredentials = true;
fetch_attribs.userName = req->args.username.data;
fetch_attribs.password = req->args.password.data;
}
}
// NOTE: It would be nice to use EMSCRIPTEN_FETCH_STREAM_DATA however
// emscripten has this note on the current version I'm using that this is
// only supported in Firefox so this is a no-go.
//
// > If passed, the intermediate streamed bytes will be passed in to the
// > onprogress() handler. If not specified, the onprogress() handler will still
// > be called, but without data bytes. Note: Firefox only as it depends on
// > 'moz-chunked-arraybuffer'.
fetch_attribs.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
fetch_attribs.onsuccess = DN_NET_EmcHTTPSuccessCallback;
fetch_attribs.onerror = DN_NET_EmcHTTPFailCallback;
fetch_attribs.onprogress = DN_NET_EmcHTTPProgressCallback;
fetch_attribs.userData = req;
}
// NOTE: Dispatch the asynchronous fetch
emscripten_fetch(&fetch_attribs, req->url.data);
return result;
}
DN_NETRequestHandle DN_NET_EmcDoWS(DN_NETCore *net, DN_Str8 url)
{
DN_Assert(emscripten_websocket_is_supported());
DN_NETRequest *req = DN_NET_EmcAllocRequest_(net);
DN_NETRequestHandle result = DN_NET_SetupRequest(req, url, /*method=*/DN_Str8Lit(""), /*args=*/nullptr, DN_NETRequestType_WS);
if (!req)
return result;
// NOTE: Setup some emscripten specific data into our request context
req->context[1] = DN_Cast(DN_UPtr) DN_ArenaNew(&req->start_response_arena, DN_NETEmcRequest, DN_ZMem_Yes);
// NOTE: Create the websocket request and dispatch it via emscripten
EmscriptenWebSocketCreateAttributes attr;
emscripten_websocket_init_create_attributes(&attr);
attr.url = req->url.data;
DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) req->context[1];
emc_request->socket = emscripten_websocket_new(&attr);
DN_Assert(emc_request->socket > 0);
emscripten_websocket_set_onopen_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnOpen);
emscripten_websocket_set_onmessage_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnMessage);
emscripten_websocket_set_onerror_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnError);
emscripten_websocket_set_onclose_callback(emc_request->socket, /*userData=*/req, DN_NET_EmcWSOnClose);
return result;
}
void DN_NET_EmcDoWSSend(DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send)
{
DN_AssertF(send == DN_NETWSSend_Binary || send == DN_NETWSSend_Text || send == DN_NETWSSend_Close,
"Unimplemented, Emscripten only supports some of the available operations");
int result = 0;
DN_NETRequest *request_ptr = DN_Cast(DN_NETRequest *) handle.handle;
if (request_ptr && request_ptr->gen == handle.gen) {
DN_Assert(request_ptr->type == DN_NETRequestType_WS);
DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request_ptr->context[1];
switch (send) {
default: DN_AssertInvalidCodePath; break;
case DN_NETWSSend_Text: {
DN_U64 pos = DN_MemListPos(request_ptr->start_response_arena.mem);
DN_Str8 data_null_terminated = DN_Str8FromStr8Arena(data, &request_ptr->start_response_arena);
result = emscripten_websocket_send_utf8_text(emc_request->socket, data_null_terminated.data);
DN_MemListPopTo(request_ptr->arena.mem, pos);
} break;
case DN_NETWSSend_Binary: {
result = emscripten_websocket_send_binary(emc_request->socket, data.data, data.size);
} break;
case DN_NETWSSend_Close: {
result = emscripten_websocket_close(emc_request->socket, 0, nullptr);
} break;
}
}
// TODO: Handle result, the header file doesn't really elucidate what this result value is
(void)result;
}
static DN_NETResponse DN_NET_EmcHandleFinishedRequest_(DN_NETCore *net, DN_NETEmcCore *emc, DN_NETRequestHandle handle, DN_NETRequest *request, DN_Arena *arena)
{
// NOTE: Generate the response, copy out the strings into the user given memory
DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request->context[1];
DN_NETResponse result = request->response;
bool end_request = true;
bool dequeue_request = true;
if (request->type == DN_NETRequestType_HTTP) {
result.body = DN_Str8FromStr8Arena(result.body, arena);
} else {
// NOTE: Get emscripten contexts
DN_NETEmcWSEvent *emc_event = emc_request->first_event;
DN_Assert(emc_event);
DN_AssertF((emc_event->state >= DN_NETResponseState_WSOpen && emc_event->state <= DN_NETResponseState_WSPong) || emc_event->state == DN_NETResponseState_Error,
"emc_event=%p", emc_event);
// NOTE: Build the result
result.state = emc_event->state;
result.request = handle;
result.body = DN_Str8FromStr8Arena(emc_event->payload, arena);
// NOTE: Advance the event list
{
if (emc_request->first_event == emc_request->last_event) {
emc_request->last_event = emc_request->last_event->next;
DN_Assert(emc_request->first_event->next == emc_request->last_event);
}
emc_request->first_event = emc_event->next;
// NOTE: If there's still an event on the request then we do not dequeue the request from the
// response list. The user can still "wait" for a response to read more data from it.
if (emc_request->first_event)
dequeue_request = false;
}
if (result.state != DN_NETResponseState_WSClose)
end_request = false;
}
// NOTE: Remove request from the response list which is doubly-linked
if (dequeue_request) {
if (request->prev) {
DN_AssertF(request->prev->next == request, "next=%p vs request=%p", request->prev->next, request);
request->prev->next = request->next;
}
if (request->next) {
DN_AssertF(request->next->prev == request, "prev=%p vs request=%p", request->next->prev, request);
request->next->prev = request->prev;
}
if (request == emc->response_list)
emc->response_list = emc->response_list->next;
request->prev = nullptr;
request->next = nullptr;
DN_Assert(emc_request->first_event == nullptr);
DN_Assert(emc_request->last_event == nullptr);
// NOTE: Deallocate the memory used in the request and reset the string builder (as all
// payload(s) have been read from the request).
if (!end_request)
DN_ArenaTempEnd(&request->start_response_arena, DN_ArenaReset_Yes);
}
if (end_request) {
DN_NET_EndFinishedRequest(request);
emscripten_websocket_delete(emc_request->socket);
emc_request->socket = 0;
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
request->next = emc->free_list;
request->prev = nullptr;
emc->free_list = request;
}
return result;
}
static DN_OSSemaphoreWaitResult DN_NET_EmcSemaphoreWait_(DN_OSSemaphore *sem, DN_U32 timeout_ms)
{
// NOTE: In emscripten you can't just block on the semaphore with 'timeout_ms' because it needs
// to yield to the javascript's event loop otherwise the fetching step cannot progress. Instead
// we use a timeout of 0 to just immediately check if the semaphore has been signalled, if not,
// then we yield to the event loop by calling sleep.
//
// Once yielded, fetch will execute and eventually in the callback it will signal the semaphore
// where it'll return and we can break out of the simulated "timeout".
DN_OSSemaphoreWaitResult result = {};
DN_U32 timeout_remaining_ms = timeout_ms;
DN_F64 begin_ms = emscripten_get_now();
for (;;) {
result = DN_OS_SemaphoreWait(sem, 0);
if (result == DN_OSSemaphoreWaitResult_Success)
break;
if (timeout_remaining_ms <= 0)
break;
emscripten_sleep(100 /*ms*/);
DN_F64 end_ms = emscripten_get_now();
DN_USize duration_ms = DN_Cast(DN_USize)(end_ms - begin_ms);
timeout_remaining_ms = timeout_remaining_ms >= duration_ms ? timeout_remaining_ms - duration_ms : 0;
begin_ms = end_ms;
}
return result;
}
DN_NETResponse DN_NET_EmcWaitForResponse(DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms)
{
DN_NETResponse result = {};
DN_NETRequest *request_ptr = DN_Cast(DN_NETRequest *) handle.handle;
if (request_ptr && request_ptr->gen == handle.gen) {
DN_NETCore *net = DN_Cast(DN_NETCore *) request_ptr->context[0];
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
DN_Assert(emc);
DN_OSSemaphoreWaitResult wait = DN_NET_EmcSemaphoreWait_(&request_ptr->completion_sem, timeout_ms);
if (wait != DN_OSSemaphoreWaitResult_Success)
return result;
result = DN_NET_EmcHandleFinishedRequest_(net, emc, handle, request_ptr, arena);
// NOTE: Decrement the global 'request done' completion semaphore since the user consumed the
// request individually.
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/);
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
}
return result;
}
DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms)
{
DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context;
DN_Assert(emc);
DN_NETResponse result = {};
DN_OSSemaphoreWaitResult wait = DN_NET_EmcSemaphoreWait_(&net->completion_sem, timeout_ms);
if (wait != DN_OSSemaphoreWaitResult_Success)
return result;
DN_AssertF(emc->response_list,
"This should be set otherwise we bumped the completion sem without queueing into the "
"done list or we forgot to wait on the global semaphore after a request finished");
// NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore
DN_NETRequest *request_ptr = emc->response_list;
DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&request_ptr->completion_sem, 0 /*timeout_ms*/);
DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result);
DN_NETRequestHandle request = {};
request.handle = DN_Cast(DN_UPtr) request_ptr;
request.gen = request_ptr->gen;
result = DN_NET_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena);
return result;
}
-17
View File
@@ -1,17 +0,0 @@
#if !defined(DN_NET_EMSCRIPTEN_H)
#define DN_NET_EMSCRIPTEN_H
#if defined(_CLANGD)
#include "dn_net.h"
#endif
DN_NETInterface DN_NET_EmcInterface();
void DN_NET_EmcInit (DN_NETCore *net, char *base, DN_U64 base_size);
void DN_NET_EmcDeinit (DN_NETCore *net);
DN_NETRequestHandle DN_NET_EmcDoHTTP (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args);
DN_NETRequestHandle DN_NET_EmcDoWS (DN_NETCore *net, DN_Str8 url);
void DN_NET_EmcDoWSSend (DN_NETRequestHandle handle, DN_Str8 data, DN_NETWSSend send);
DN_NETResponse DN_NET_EmcWaitForResponse (DN_NETRequestHandle handle, DN_Arena *arena, DN_U32 timeout_ms);
DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms);
#endif // DN_NET_EMSCRIPTEN_H
+2 -4
View File
@@ -1,8 +1,6 @@
#if defined(_CLANGD)
#define DN_H_WITH_OS 1
#include "../dn.h"
#include "../Extra/dn_net.h"
#include "../Extra/dn_net_curl.h"
#define DN_WITH_OS 1
#include "dn.h"
#include "../Standalone/dn_utest.h"
#define DN_UNIT_TESTS_WITH_KECCAK
+6 -12
View File
@@ -1,24 +1,18 @@
#if defined(DN_UNIT_TESTS_WITH_CURL)
#if DN_WITH_NET_CURL
#define DN_NO_WINDOWS_H_REPLACEMENT_HEADER
#endif
#define DN_ARENA_TEMP_MEM_UAF_GUARD 1
#define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 0
#define DN_H_WITH_OS 1
#define DN_H_WITH_CORE 1
#define DN_H_WITH_HASH 1
#define DN_H_WITH_HELPERS 1
#define DN_H_WITH_ASYNC 1
#define DN_H_WITH_NET 1
#define DN_WITH_OS 1
#define DN_WITH_NET 1
#include "../dn.h"
#include "../dn.cpp"
#if defined(DN_UNIT_TESTS_WITH_CURL)
#if DN_WITH_NET_CURL
#define CURL_STATICLIB
#include <curl/curl.h>
#include "../Extra/dn_net_curl.h"
#include "../Extra/dn_net_curl.cpp"
#endif
#include "../dn.cpp"
#if defined(DN_PLATFORM_EMSCRIPTEN)
#include <emscripten/emscripten.h>
@@ -36,7 +30,7 @@ DN_MSVC_WARNING_DISABLE(6262) // Function uses '29804' bytes of stack. Consider
int main(int, char**)
{
DN_Core dn = {};
DN_Init(&dn, DN_InitFlags_LogAllFeatures | DN_InitFlags_OS | DN_InitFlags_ThreadContext, DN_TCInitArgsDefault());
DN_Init(&dn, DN_InitFlags_LogAllFeatures | DN_InitFlags_OS, DN_TCInitArgsDefault());
DN_TST_RunSuite(DN_TSTPrint_Yes);
return 0;
}
-1414
View File
File diff suppressed because it is too large Load Diff
-575
View File
@@ -1,575 +0,0 @@
#if !defined(DN_OS_H)
#define DN_OS_H
#if defined(_CLANGD)
#define DN_H_WITH_OS 1
#include "../dn.h"
#endif
#include <new> // operator new
#if !defined(DN_OS_WIN32) || defined(DN_OS_WIN32_USE_PTHREADS)
#include <pthread.h>
#include <semaphore.h>
#endif
#if defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN)
#include <errno.h> // errno
#include <fcntl.h> // O_RDONLY ... etc
#include <sys/ioctl.h> // ioctl
#include <sys/mman.h> // mmap
#include <sys/random.h> // getrandom
#include <sys/stat.h> // stat
#include <sys/types.h> // pid_t
#include <sys/wait.h> // waitpid
#include <time.h> // clock_gettime, nanosleep
#include <unistd.h> // access, gettid, write
#if !defined(DN_PLATFORM_EMSCRIPTEN)
#include <linux/fs.h> // FICLONE
#include <sys/sendfile.h> // sendfile
#endif
#endif
extern DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count];
struct DN_OSTimer /// Record time between two time-points using the OS's performance counter.
{
DN_U64 start;
DN_U64 end;
};
// NOTE: DN_OSFile
enum DN_OSPathInfoType
{
DN_OSPathInfoType_Unknown,
DN_OSPathInfoType_Directory,
DN_OSPathInfoType_File,
};
struct DN_OSPathInfo
{
bool exists;
DN_OSPathInfoType type;
DN_U64 create_time_in_s;
DN_U64 last_write_time_in_s;
DN_U64 last_access_time_in_s;
DN_U64 size;
};
struct DN_OSDirIterator
{
void *handle;
DN_Str8 file_name;
char buffer[512];
};
// NOTE: R/W Stream API
struct DN_OSFileRead
{
bool success;
DN_USize bytes_read;
};
struct DN_OSFile
{
bool error;
void *handle;
};
enum DN_OSFileOpen
{
DN_OSFileOpen_CreateAlways, // Create file if it does not exist, otherwise, zero out the file and open
DN_OSFileOpen_OpenIfExist, // Open file at path only if it exists
DN_OSFileOpen_OpenAlways, // Open file at path, create file if it does not exist
};
typedef DN_U32 DN_OSFileAccess;
enum DN_OSFileAccess_
{
DN_OSFileAccess_Read = 1 << 0,
DN_OSFileAccess_Write = 1 << 1,
DN_OSFileAccess_Execute = 1 << 2,
DN_OSFileAccess_AppendOnly = 1 << 3, // This flag cannot be combined with any other access mode
DN_OSFileAccess_ReadWrite = DN_OSFileAccess_Read | DN_OSFileAccess_Write,
DN_OSFileAccess_All = DN_OSFileAccess_ReadWrite | DN_OSFileAccess_Execute | DN_OSFileAccess_AppendOnly,
};
// NOTE: DN_OSPath
#if !defined(DN_OSPathSeperator)
#if defined(DN_OS_WIN32)
#define DN_OSPathSeperator "\\"
#else
#define DN_OSPathSeperator "/"
#endif
#define DN_OSPathSeperatorString DN_Str8Lit(DN_OSPathSeperator)
#endif
struct DN_OSPathLink
{
DN_Str8 string;
DN_OSPathLink *next;
DN_OSPathLink *prev;
};
struct DN_OSPath
{
bool has_prefix_path_separator;
DN_OSPathLink *head;
DN_OSPathLink *tail;
DN_USize string_size;
DN_U16 links_size;
};
// NOTE: DN_OSExec
typedef DN_U32 DN_OSExecFlags;
enum DN_OSExecFlags_
{
DN_OSExecFlags_Nil = 0,
DN_OSExecFlags_SaveStdout = 1 << 0,
DN_OSExecFlags_SaveStderr = 1 << 1,
DN_OSExecFlags_SaveOutput = DN_OSExecFlags_SaveStdout | DN_OSExecFlags_SaveStderr,
DN_OSExecFlags_MergeStderrToStdout = 1 << 2 | DN_OSExecFlags_SaveOutput,
};
struct DN_OSExecAsyncHandle
{
DN_OSExecFlags exec_flags;
DN_U32 os_error_code;
DN_U32 exit_code;
void *process;
void *stdout_read;
void *stdout_write;
void *stderr_read;
void *stderr_write;
};
struct DN_OSExecResult
{
bool finished;
DN_Str8 stdout_text;
DN_Str8 stderr_text;
DN_U32 os_error_code;
DN_U32 exit_code;
};
struct DN_OSExecArgs
{
DN_OSExecFlags flags;
DN_Str8 working_dir;
DN_Str8Slice environment;
};
// NOTE: DN_OSSemaphore
DN_U32 const DN_OS_SEMAPHORE_INFINITE_TIMEOUT = UINT32_MAX;
struct DN_OSSemaphore
{
DN_U64 handle;
};
struct DN_OSBarrier
{
DN_U64 handle;
};
enum DN_OSSemaphoreWaitResult
{
DN_OSSemaphoreWaitResult_Failed,
DN_OSSemaphoreWaitResult_Success,
DN_OSSemaphoreWaitResult_Timeout,
};
struct DN_OSMutex
{
DN_U64 handle;
};
struct DN_OSConditionVariable
{
DN_U64 handle;
};
// NOTE: DN_OSThread
typedef DN_I32(DN_OSThreadFunc)(struct DN_OSThread *);
struct DN_OSThreadLane
{
DN_USize index;
DN_USize count;
DN_OSBarrier barrier;
void* shared_mem;
};
struct DN_OSThreadLaneway
{
DN_OSThread* threads;
DN_USize threads_count;
DN_UPtr* shared_mem;
DN_OSBarrier barrier;
};
struct DN_OSThread
{
DN_Str8x64 name;
DN_TCCore context;
DN_OSThreadLane lane;
bool is_lane_set;
void *handle;
DN_U64 thread_id;
void *user_context;
DN_OSThreadFunc *func;
DN_OSSemaphore init_semaphore;
DN_TCInitArgs tc_init_args;
};
struct DN_OSCore
{
DN_CPUReport cpu_report;
// NOTE: Logging
bool log_to_file; // Output logs to file as well as standard out
DN_OSFile log_file; // TODO(dn): Hmmm, how should we do this... ?
DN_TicketMutex log_file_mutex; // Is locked when instantiating the log_file for the first time
bool log_no_colour; // Disable colours in the logging output
DN_TicketMutex log_mutex;
// NOTE: OS
DN_U32 logical_processor_count;
DN_U32 page_size;
DN_U32 alloc_granularity;
// NOTE: Memory
// Total OS mem allocs in lifetime of program (e.g. malloc, VirtualAlloc, HeapAlloc ...). This
// only includes allocations routed through the library such as the growing nature of arenas or
// using the memory allocation routines in the library like DN_OS_MemCommit and so forth.
DN_U64 vmem_allocs_total;
DN_U64 vmem_allocs_frame; // Total OS virtual memory allocs since the last 'DN_Core_FrameBegin' was invoked
DN_U64 mem_allocs_total;
DN_U64 mem_allocs_frame; // Total OS heap allocs since the last 'DN_Core_FrameBegin' was invoked
DN_MemList mem;
DN_Arena arena;
void *platform_context;
};
struct DN_OSDiskSpace
{
bool success;
DN_U64 avail;
DN_U64 size;
};
DN_API DN_MemFuncs DN_MemFuncsFromType (DN_MemFuncsType type);
DN_API DN_MemFuncs DN_MemFuncsDefault ();
DN_API DN_MemList DN_MemListFromHeap (DN_U64 size, DN_MemFlags flags);
DN_API DN_MemList DN_MemListFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags);
DN_API DN_Arena DN_ArenaFromHeap (DN_U64 wize, DN_MemFlags flags);
DN_API DN_Arena DN_ArenaFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags);
DN_API DN_Str8 DN_Str8FromHeapF (DN_FMT_ATTRIB char const *fmt, ...);
DN_API DN_Str8 DN_Str8FromHeap (DN_USize size, DN_ZMem z_mem);
DN_API DN_Str8 DN_Str8BuilderBuildFromHeap (DN_Str8Builder const *builder);
DN_API void DN_OS_LogPrint (DN_LogTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args);
DN_API void DN_OS_SetLogPrintFuncToOS ();
DN_API void * DN_OS_MemReserve (DN_USize size, DN_MemCommit commit, DN_MemPage page_flags);
DN_API bool DN_OS_MemCommit (void *ptr, DN_USize size, DN_U32 page_flags);
DN_API void DN_OS_MemDecommit (void *ptr, DN_USize size);
DN_API void DN_OS_MemRelease (void *ptr, DN_USize size);
DN_API int DN_OS_MemProtect (void *ptr, DN_USize size, DN_U32 page_flags);
DN_API void * DN_OS_MemAlloc (DN_USize size, DN_ZMem z_mem);
DN_API void DN_OS_MemDealloc (void *ptr);
DN_API DN_Date DN_OS_DateLocalTimeNow ();
DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8Now (char date_separator = '-', char hms_separator = ':');
DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8 (DN_Date time, char date_separator = '-', char hms_separator = ':');
DN_API DN_U64 DN_OS_DateUnixTimeNs ();
#define DN_OS_DateUnixTimeUs() (DN_OS_DateUnixTimeNs() / 1000)
#define DN_OS_DateUnixTimeMs() (DN_OS_DateUnixTimeNs() / (1000 * 1000))
#define DN_OS_DateUnixTimeS() (DN_OS_DateUnixTimeNs() / (1000 * 1000 * 1000))
DN_API DN_U64 DN_OS_DateUnixTimeSFromLocalDate (DN_Date date);
DN_API DN_U64 DN_OS_DateLocalUnixTimeSFromUnixTimeS (DN_U64 unix_ts_s);
DN_API void DN_OS_GenBytesSecure (void *buffer, DN_U32 size);
DN_API bool DN_OS_SetEnvVar (DN_Str8 name, DN_Str8 value);
DN_API DN_OSDiskSpace DN_OS_DiskSpace (DN_Str8 path);
DN_API DN_Str8 DN_OS_EXEPath (DN_Arena *arena);
DN_API DN_Str8 DN_OS_EXEDir (DN_Arena *arena);
DN_API void DN_OS_SleepMs (DN_UInt milliseconds);
DN_API DN_U64 DN_OS_PerfCounterNow ();
DN_API DN_U64 DN_OS_PerfCounterFrequency ();
DN_API DN_F64 DN_OS_PerfCounterS (DN_U64 begin, uint64_t end);
DN_API DN_F64 DN_OS_PerfCounterMs (DN_U64 begin, uint64_t end);
DN_API DN_F64 DN_OS_PerfCounterUs (DN_U64 begin, uint64_t end);
DN_API DN_F64 DN_OS_PerfCounterNs (DN_U64 begin, uint64_t end);
DN_API DN_OSTimer DN_OS_TimerBegin ();
DN_API void DN_OS_TimerEnd (DN_OSTimer *timer);
DN_API DN_F64 DN_OS_TimerS (DN_OSTimer timer);
DN_API DN_F64 DN_OS_TimerMs (DN_OSTimer timer);
DN_API DN_F64 DN_OS_TimerUs (DN_OSTimer timer);
DN_API DN_F64 DN_OS_TimerNs (DN_OSTimer timer);
DN_API DN_U64 DN_OS_EstimateTSCPerSecond (uint64_t duration_ms_to_gauge_tsc_frequency);
DN_API bool DN_OS_FileCopy (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err);
DN_API bool DN_OS_FileMove (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err);
DN_API DN_OSFile DN_OS_FileOpen (DN_Str8 path, DN_OSFileOpen open_mode, DN_OSFileAccess access, DN_ErrSink *err);
DN_API DN_OSFileRead DN_OS_FileRead (DN_OSFile *file, void *buffer, DN_USize size, DN_ErrSink *err);
DN_API bool DN_OS_FileWritePtr (DN_OSFile *file, void const *data, DN_USize size, DN_ErrSink *err);
DN_API bool DN_OS_FileWrite (DN_OSFile *file, DN_Str8 buffer, DN_ErrSink *err);
DN_API bool DN_OS_FileWriteFV (DN_OSFile *file, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args);
DN_API bool DN_OS_FileWriteF (DN_OSFile *file, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...);
DN_API bool DN_OS_FileFlush (DN_OSFile *file, DN_ErrSink *err);
DN_API void DN_OS_FileClose (DN_OSFile *file);
DN_API DN_Str8 DN_OS_FileReadAll (DN_Allocator allocator, DN_Str8 path, DN_ErrSink *err);
DN_API DN_Str8 DN_OS_FileReadAllArena (DN_Arena *arena, DN_Str8 path, DN_ErrSink *err);
DN_API DN_Str8 DN_OS_FileReadAllPool (DN_Pool *pool, DN_Str8 path, DN_ErrSink *err);
DN_API bool DN_OS_FileWriteAll (DN_Str8 path, DN_Str8 buffer, DN_ErrSink *err);
DN_API bool DN_OS_FileWriteAllFV (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args);
DN_API bool DN_OS_FileWriteAllF (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...);
DN_API bool DN_OS_FileWriteAllSafe (DN_Str8 path, DN_Str8 buffer, DN_ErrSink *err);
DN_API bool DN_OS_FileWriteAllSafeFV (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args);
DN_API bool DN_OS_FileWriteAllSafeF (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...);
DN_API DN_Str8 DN_OS_Str8FromPathInfoType (DN_OSPathInfoType type);
DN_API DN_OSPathInfo DN_OS_PathInfo (DN_Str8 path);
DN_API bool DN_OS_PathIsOlderThan (DN_Str8 file, DN_Str8 check_against);
DN_API bool DN_OS_PathDelete (DN_Str8 path);
DN_API bool DN_OS_PathIsFile (DN_Str8 path);
DN_API bool DN_OS_PathIsDir (DN_Str8 path);
DN_API bool DN_OS_PathMakeDir (DN_Str8 path);
DN_API bool DN_OS_PathIterateDir (DN_Str8 path, DN_OSDirIterator *it);
DN_API bool DN_OS_PathAddRef (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path);
DN_API bool DN_OS_PathAdd (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path);
DN_API bool DN_OS_PathAddF (DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...);
DN_API bool DN_OS_PathPop (DN_OSPath *fs_path);
DN_API DN_Str8 DN_OS_PathBuildWithSeparator (DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator);
DN_API DN_Str8 DN_OS_PathTo (DN_Arena *arena, DN_Str8 path, DN_Str8 path_separtor);
DN_API DN_Str8 DN_OS_PathToF (DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...);
DN_API DN_Str8 DN_OS_Path (DN_Arena *arena, DN_Str8 path);
DN_API DN_Str8 DN_OS_PathF (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...);
#define DN_OS_PathBuildFwdSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_Str8Lit("/"))
#define DN_OS_PathBuildBackSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_Str8Lit("\\"))
#define DN_OS_PathBuild(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_OSPathSeparatorString)
DN_API void DN_OS_Exit (int32_t exit_code);
DN_API DN_OSExecResult DN_OS_ExecPump (DN_OSExecAsyncHandle handle, char *stdout_buffer, size_t *stdout_size, char *stderr_buffer, size_t *stderr_size, DN_U32 timeout_ms, DN_ErrSink *err);
DN_API DN_OSExecResult DN_OS_ExecWait (DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_ErrSink *err);
DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_ErrSink *err);
DN_API DN_OSExecResult DN_OS_Exec (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena, DN_ErrSink *err);
DN_API DN_OSExecResult DN_OS_ExecOrAbort (DN_Str8Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena);
DN_API DN_OSSemaphore DN_OS_SemaphoreInit (DN_U32 initial_count);
DN_API void DN_OS_SemaphoreDeinit (DN_OSSemaphore *semaphore);
DN_API void DN_OS_SemaphoreIncrement (DN_OSSemaphore *semaphore, DN_U32 amount);
DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait (DN_OSSemaphore *semaphore, DN_U32 timeout_ms);
DN_API DN_OSBarrier DN_OS_BarrierInit (DN_U32 thread_count);
DN_API void DN_OS_BarrierDeinit (DN_OSBarrier *barrier);
DN_API void DN_OS_BarrierWait (DN_OSBarrier *barrier);
DN_API DN_OSMutex DN_OS_MutexInit ();
DN_API void DN_OS_MutexDeinit (DN_OSMutex *mutex);
DN_API void DN_OS_MutexLock (DN_OSMutex *mutex);
DN_API void DN_OS_MutexUnlock (DN_OSMutex *mutex);
#define DN_OS_MutexScope(mutex) DN_DeferLoop(DN_OS_MutexLock(mutex), DN_OS_MutexUnlock(mutex))
DN_API DN_OSConditionVariable DN_OS_ConditionVariableInit ();
DN_API void DN_OS_ConditionVariableDeinit (DN_OSConditionVariable *cv);
DN_API bool DN_OS_ConditionVariableWait (DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 sleep_ms);
DN_API bool DN_OS_ConditionVariableWaitUntil (DN_OSConditionVariable *cv, DN_OSMutex *mutex, DN_U64 end_ts_ms);
DN_API void DN_OS_ConditionVariableSignal (DN_OSConditionVariable *cv);
DN_API void DN_OS_ConditionVariableBroadcast (DN_OSConditionVariable *cv);
DN_API bool DN_OS_ThreadInit (DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_TCInitArgs tc_init_args, void *user_context);
DN_API bool DN_OS_ThreadJoin (DN_OSThread *thread, DN_TCDeinitArenas deinit_arenas);
DN_API DN_U32 DN_OS_ThreadID ();
DN_API void DN_OS_ThreadSetNameFmt (char const *fmt, ...);
// NOTE: Thread lanes provide an abstraction to represent the concept of programming a CPU like a
// GPU, e.g. SIMT (Single Instruction Multiple Threads). The lane terminology is popularised by Ryan
// Fleury. SIMT is formally defined as
//
// Threads are grouped into warps/wavefronts (typically 32 or 64 threads) that execute the same
// instruction in lockstep, but each thread operates on different data and maintains its own state
//
// The individual threads in a wavefront on the CPU side are colloquially dubbed "lanes" and a
// thread lane here contains the necessary state to facilitate this such as the current index in the
// wavefront and synchronisation primitives to coordinate the different lanes together.
//
// The idea is to write code in a single-threaded manner (linear execution) but across multiple
// threads so that the default is all execution paths are inherently multi-threaded by default. Opt
// out of parallelism instead of opt in. This optimises for the trend of core counts increasing
// whilst clock counts remain static.
//
// A laneway is a helper function to initialise the number of requested OS threads/lanes upfront and
// setup the required synchronisation primitives. It can then be dispatched all the threads which
// start executing the `entry_point` in parallel.
//
// API
// DN_OS_ThreadLaneSync
// A blocking call to synchronise the program-counter of all other lanes in the laneway to this
// function call invocation (using an OS barrier). Optionally pass in the pointer to a pointer
// `ptr_to_share` to broadcast the pointer from one lanes to the others. The lane that wishes
// to broadcast the pointer must have a non-null pointer, all other lanes must pass in a
// non-null pointer. A typical use case might look like:
/*
DN_OSThreadLane *lane = DN_OS_TCThreadLane(); // Get lane from current (t)hread (c)context
// NOTE: Allocate buffer in lane 0
DN_U8 *buffer = nullptr;
if (lane->index == 0)
buffer = DN_ArenaNewArray(DN_TCMainArena(), DN_U8, DN_Gigabytes(1), DN_ZMem_No);
// NOTE: Lane 0 broadcasts the `buffer` pointer to lane 1..N
DN_OS_ThreadLaneSync(lane, &buffer);
// NOTE: We use LaneRange to divide the buffer into equal sized chunks that each lane can
// write into without clobbering over each other.
DN_V2USize range = DN_OS_ThreadLaneRange(lane, DN_Gigabytes(1));
for (DN_USize index = range.begin; index < range.end; index++) { buffer[index] = index; }
*/
// In this example, lane 0 will allocate a 1GiB buffer pass in a `buffer` to
// DN_OS_ThreadLaneSync` that is non-null. Lanes 1->N will skip the branch (because their lanes
// indexes are 1..N) and invoke `DN_OS_ThreadLaneSync` with a nullptr `buffer`. After the
// blocking call is complete, lanes 0->N will now have synchronised the `buffer` pointer and all
// lanes point to the 1GiB range allocated in lane 0's allocator.
//
// Additionally we demonstrate `DN_OS_ThreadLaneRange` which does math behind the scenes to
// divide the buffer up and assign each lane their own indices in the buffer that they can work
// on in parallel without clobbering each others work.
//
// DN_OS_ThreadLaneRange
// Calculates the range of values the current lane in the laneway should execute. For example if
// you have 128 items and 16 threads each lane will receive the following `DN_V2USize` range:
// Lane 0 => [0, 8)
// Lane 1 => [8, 16)
// ...
// Lane 16 => [120, 128)
DN_API DN_OSThreadLane DN_OS_ThreadLaneInit (DN_USize index, DN_USize thread_count, DN_OSBarrier barrier, DN_UPtr *share_mem);
DN_API void DN_OS_ThreadLaneSync (DN_OSThreadLane *lane, void **ptr_to_share);
DN_API DN_V2USize DN_OS_ThreadLaneRange (DN_OSThreadLane const *lane, DN_USize values_count);
DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArgs (DN_OSThread* threads, DN_USize threads_count, DN_UPtr* shared_mem);
DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArena (DN_USize threads_count, DN_Arena* arena);
DN_API void DN_OS_ThreadLanewayDispatch (DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_TCInitArgs tc_init_args, void *user_context);
DN_API void DN_OS_ThreadLanewayJoin (DN_OSThreadLaneway *laneway, DN_TCDeinitArenas deinit_arenas);
DN_API DN_OSThreadLane* DN_OS_TCThreadLane ();
DN_API void DN_OS_TCThreadLaneSync (void **ptr_to_share);
DN_API DN_OSThreadLane DN_OS_TCThreadLaneEquip (DN_OSThreadLane lane);
enum DN_OSAsyncPriority
{
DN_OSAsyncPriority_Low,
DN_OSAsyncPriority_High,
DN_OSAsyncPriority_Count,
};
struct DN_OSAsyncCore
{
DN_OSMutex ring_mutex;
DN_OSConditionVariable ring_write_cv;
DN_OSSemaphore worker_sem;
DN_Ring ring;
DN_OSThread *threads;
DN_U32 thread_count;
DN_U32 busy_threads;
DN_U32 join_threads;
};
struct DN_OSAsyncWorkArgs
{
DN_OSThread *thread;
void *input;
};
typedef void(DN_OSAsyncWorkFunc)(DN_OSAsyncWorkArgs work_args);
struct DN_OSAsyncWork
{
DN_OSAsyncWorkFunc *func;
void *input;
void *output;
};
struct DN_OSAsyncTask
{
bool queued;
DN_OSAsyncWork work;
DN_OSSemaphore completion_sem;
};
DN_API void DN_OS_AsyncInit (DN_OSAsyncCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size);
DN_API void DN_OS_AsyncDeinit (DN_OSAsyncCore *async);
DN_API bool DN_OS_AsyncQueueWork(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms);
DN_API DN_OSAsyncTask DN_OS_AsyncQueueTask(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms);
DN_API bool DN_OS_AsyncWaitTask (DN_OSAsyncTask *task, DN_U32 timeout_ms);
// NOTE: DN_OSPrint
enum DN_OSPrintDest
{
DN_OSPrintDest_Out,
DN_OSPrintDest_Err,
};
// NOTE: Print Macros
#define DN_OS_PrintOut(string) DN_OS_Print(DN_OSPrintDest_Out, string)
#define DN_OS_PrintOutF(fmt, ...) DN_OS_PrintF(DN_OSPrintDest_Out, fmt, ##__VA_ARGS__)
#define DN_OS_PrintOutFV(fmt, args) DN_OS_PrintFV(DN_OSPrintDest_Out, fmt, args)
#define DN_OS_PrintOutStyle(style, string) DN_OS_PrintStyle(DN_OSPrintDest_Out, style, string)
#define DN_OS_PrintOutFStyle(style, fmt, ...) DN_OS_PrintFStyle(DN_OSPrintDest_Out, style, fmt, ##__VA_ARGS__)
#define DN_OS_PrintOutFVStyle(style, fmt, args, ...) DN_OS_PrintFVStyle(DN_OSPrintDest_Out, style, fmt, args)
#define DN_OS_PrintOutLn(string) DN_OS_PrintLn(DN_OSPrintDest_Out, string)
#define DN_OS_PrintOutLnF(fmt, ...) DN_OS_PrintLnF(DN_OSPrintDest_Out, fmt, ##__VA_ARGS__)
#define DN_OS_PrintOutLnFV(fmt, args) DN_OS_PrintLnFV(DN_OSPrintDest_Out, fmt, args)
#define DN_OS_PrintOutLnStyle(style, string) DN_OS_PrintLnStyle(DN_OSPrintDest_Out, style, string);
#define DN_OS_PrintOutLnFStyle(style, fmt, ...) DN_OS_PrintLnFStyle(DN_OSPrintDest_Out, style, fmt, ##__VA_ARGS__)
#define DN_OS_PrintOutLnFVStyle(style, fmt, args) DN_OS_PrintLnFVStyle(DN_OSPrintDest_Out, style, fmt, args);
#define DN_OS_PrintErr(string) DN_OS_Print(DN_OSPrintDest_Err, string)
#define DN_OS_PrintErrF(fmt, ...) DN_OS_PrintF(DN_OSPrintDest_Err, fmt, ##__VA_ARGS__)
#define DN_OS_PrintErrFV(fmt, args) DN_OS_PrintFV(DN_OSPrintDest_Err, fmt, args)
#define DN_OS_PrintErrStyle(style, string) DN_OS_PrintStyle(DN_OSPrintDest_Err, style, string)
#define DN_OS_PrintErrFStyle(style, fmt, ...) DN_OS_PrintFStyle(DN_OSPrintDest_Err, style, fmt, ##__VA_ARGS__)
#define DN_OS_PrintErrFVStyle(style, fmt, args, ...) DN_OS_PrintFVStyle(DN_OSPrintDest_Err, style, fmt, args)
#define DN_OS_PrintErrLn(string) DN_OS_PrintLn(DN_OSPrintDest_Err, string)
#define DN_OS_PrintErrLnF(fmt, ...) DN_OS_PrintLnF(DN_OSPrintDest_Err, fmt, ##__VA_ARGS__)
#define DN_OS_PrintErrLnFV(fmt, args) DN_OS_PrintLnFV(DN_OSPrintDest_Err, fmt, args)
#define DN_OS_PrintErrLnStyle(style, string) DN_OS_PrintLnStyle(DN_OSPrintDest_Err, style, string);
#define DN_OS_PrintErrLnFStyle(style, fmt, ...) DN_OS_PrintLnFStyle(DN_OSPrintDest_Err, style, fmt, ##__VA_ARGS__)
#define DN_OS_PrintErrLnFVStyle(style, fmt, args) DN_OS_PrintLnFVStyle(DN_OSPrintDest_Err, style, fmt, args);
// NOTE: Print
DN_API void DN_OS_Print (DN_OSPrintDest dest, DN_Str8 string);
DN_API void DN_OS_PrintF (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...);
DN_API void DN_OS_PrintFV (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args);
DN_API void DN_OS_PrintStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string);
DN_API void DN_OS_PrintFStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...);
DN_API void DN_OS_PrintFVStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args);
DN_API void DN_OS_PrintLn (DN_OSPrintDest dest, DN_Str8 string);
DN_API void DN_OS_PrintLnF (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...);
DN_API void DN_OS_PrintLnFV (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args);
DN_API void DN_OS_PrintLnStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_Str8 string);
DN_API void DN_OS_PrintLnFStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, ...);
DN_API void DN_OS_PrintLnFVStyle (DN_OSPrintDest dest, DN_LogStyle style, DN_FMT_ATTRIB char const *fmt, va_list args);
#endif // !defined(DN_OS_H)
+11790 -30
View File
File diff suppressed because it is too large Load Diff
+4330 -33
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -34,7 +34,7 @@ pushd %build_dir%
:: GR- Disable C RTTI
:: Oi Use CPU Intrinsics
:: Z7 Combine multi-debug files to one debug file
set flags=%flags% -D DN_UNIT_TESTS_WITH_KECCAK -D DN_UNIT_TESTS_WITH_NET %script_dir%\Source\Extra\dn_tests_main.cpp
set flags=%flags% -D DN_UNIT_TESTS_WITH_KECCAK %script_dir%\Source\Extra\dn_tests_main.cpp
set msvc_driver_flags=-EHa -GR- -Od -Oi -Z7 -wd4201 -W4 -nologo %flags% -fsanitize=address
where /q emcc && (
@@ -46,7 +46,7 @@ pushd %build_dir%
echo [BUILD] MSVC cl detected, compiling ...
set msvc_cmd=cl -MTd %msvc_driver_flags% -analyze -Fe:dn_unit_tests_msvc -Fo:dn_unit_tests_msvc
if exist %build_dir%/Curl/Install/lib/libcurl-d.lib (
set msvc_cmd=!msvc_cmd! -D DN_UNIT_TESTS_WITH_CURL -I %build_dir%/Curl/Install/include %build_dir%/Curl/Install/lib/libcurl-d.lib crypt32.lib ws2_32.lib advapi32.lib wldap32.lib iphlpapi.lib secur32.lib
set msvc_cmd=!msvc_cmd! -D DN_WITH_NET_CURL=1 -I %build_dir%/Curl/Install/include %build_dir%/Curl/Install/lib/libcurl-d.lib crypt32.lib ws2_32.lib advapi32.lib wldap32.lib iphlpapi.lib secur32.lib
)
set msvc_cmd=!msvc_cmd! -link
+2 -3
View File
@@ -1,5 +1,4 @@
#define DN_H_WITH_OS 1
#define DN_H_WITH_CORE 1
#define DN_WITH_OS 1
#if defined(USE_SINGLE_HEADER)
#include "Single-Header/dn_single_header.h"
#else
@@ -91,7 +90,7 @@ static void AppendCppFileLineByLine(DN_Str8Builder *dest, DN_Str8 cpp_path)
int main(int argc, char **argv)
{
DN_Core dn = {};
DN_Init(&dn, DN_InitFlags_OS | DN_InitFlags_ThreadContext, DN_TCInitArgsDefault());
DN_Init(&dn, DN_InitFlags_OS, DN_TCInitArgsDefault());
if (argc != 3) {
DN_OS_PrintErrF("USAGE: %s <path/to/dn/Source> <output_dir>", argv[0]);