Start merging things back into a mega header to simplify everything
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Vendored
-4450
File diff suppressed because it is too large
Load Diff
Vendored
-1248
File diff suppressed because it is too large
Load Diff
-1905
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
File diff suppressed because it is too large
Load Diff
+4330
-33
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user