Browse Source

sokol_fetch.h: add new-style logging, enabel request validation in release mode

Andre Weissflog 2 năm trước cách đây
mục cha
commit
138280be7c
2 tập tin đã thay đổi với 257 bổ sung123 xóa
  1. 2 1
      README.md
  2. 255 122
      sokol_fetch.h

+ 2 - 1
README.md

@@ -327,6 +327,7 @@ Simple C99 example loading a file into a static buffer:
 
 
 ```c
 ```c
 #include "sokol_fetch.h"
 #include "sokol_fetch.h"
+#include "sokol_log.h"
 
 
 static void response_callback(const sfetch_response*);
 static void response_callback(const sfetch_response*);
 
 
@@ -337,7 +338,7 @@ static uint8_t buffer[MAX_FILE_SIZE];
 static void init(void) {
 static void init(void) {
     ...
     ...
     // setup sokol-fetch with default config:
     // setup sokol-fetch with default config:
-    sfetch_setup(&(sfetch_desc_t){0});
+    sfetch_setup(&(sfetch_desc_t){ .logger.func = slog_func });
 
 
     // start loading a file into a statically allocated buffer:
     // start loading a file into a statically allocated buffer:
     sfetch_send(&(sfetch_request_t){
     sfetch_send(&(sfetch_request_t){

+ 255 - 122
sokol_fetch.h

@@ -75,9 +75,11 @@
 
 
     (1) initialize sokol-fetch with default parameters (but NOTE that the
     (1) initialize sokol-fetch with default parameters (but NOTE that the
         default setup parameters provide a safe-but-slow "serialized"
         default setup parameters provide a safe-but-slow "serialized"
-        operation):
+        operation). In order to see any logging output in case or errors
+        you should always provide a logging function
+        (such as 'slog_func' from sokol_log.h):
 
 
-        sfetch_setup(&(sfetch_desc_t){ 0 });
+        sfetch_setup(&(sfetch_desc_t){ .logger.func = slog_func });
 
 
     (2) send a fetch-request to load a file from the current directory
     (2) send a fetch-request to load a file from the current directory
         into a buffer big enough to hold the entire file content:
         into a buffer big enough to hold the entire file content:
@@ -834,26 +836,46 @@
     was called, so you don't need to worry about thread-safety.
     was called, so you don't need to worry about thread-safety.
 
 
 
 
-    LOG FUNCTION OVERRIDE
-    =====================
-    You can override the log function at initialization time like this:
+    ERROR REPORTING AND LOGGING
+    ===========================
+    To get any logging information at all you need to provide a logging callback in the setup call,
+    the easiest way is to use sokol_log.h:
 
 
-        void my_log(const char* message, void* user_data) {
-            printf("sfetch says: \s\n", message);
+        #include "sokol_log.h"
+
+        sfetch_setup(&(sfetch_desc_t){
+            // ...
+            .logger.func = slog_func
+        });
+
+    To override logging with your own callback, first write a logging function like this:
+
+        void my_log(const char* tag,                // e.g. 'sfetch'
+                    uint32_t log_level,             // 0=panic, 1=error, 2=warn, 3=info
+                    uint32_t log_item_id,           // SFETCH_LOGITEM_*
+                    const char* message_or_null,    // a message string, may be nullptr in release mode
+                    uint32_t line_nr,               // line number in sokol_audio.h
+                    const char* filename_or_null,   // source filename, may be nullptr in release mode
+                    void* user_data)
+        {
+            ...
         }
         }
 
 
-        ...
-            sfetch_setup(&(sfetch_desc_t){
-                // ...
-                .logger = {
-                    .log_cb = my_log,
-                    .user_data = ...,
-                }
-            });
-        ...
+    ...and then setup sokol-fetch like this:
 
 
-    If no overrides are provided, puts will be used on most platforms.
-    On Android, __android_log_write will be used instead.
+        sfetch_setup(&(sfetch_desc_t){
+            .logger = {
+                .func = my_log,
+                .user_data = my_user_data,
+            }
+        });
+
+    The provided logging function must be reentrant (e.g. be callable from
+    different threads).
+
+    If you don't want to provide your own custom logger it is highly recommended to use
+    the standard logger in sokol_log.h instead, otherwise you won't see any warnings or
+    errors.
 
 
 
 
     FUTURE PLANS / V2.0 IDEA DUMP
     FUTURE PLANS / V2.0 IDEA DUMP
@@ -921,6 +943,55 @@
 extern "C" {
 extern "C" {
 #endif
 #endif
 
 
+/*
+    sfetch_log_item_t
+
+    Log items are defined via X-Macros, and expanded to an
+    enum 'saudio_log_item', and in debug mode only,
+    corresponding strings.
+
+    Used as parameter in the logging callback.
+*/
+#define _SFETCH_LOG_ITEMS \
+    _SFETCH_XMACRO(OK) \
+    _SFETCH_XMACRO(MALLOC_FAILED) \
+    _SFETCH_XMACRO(FILE_PATH_UTF8_DECODING_FAILED) \
+    _SFETCH_XMACRO(SEND_QUEUE_FULL) \
+    _SFETCH_XMACRO(REQUEST_CHANNEL_TOO_BIG) \
+    _SFETCH_XMACRO(REQUEST_PATH_IS_NULL) \
+    _SFETCH_XMACRO(REQUEST_PATH_TOO_LONG) \
+    _SFETCH_XMACRO(REQUEST_CALLBACK_MISSING) \
+    _SFETCH_XMACRO(REQUEST_CHUNK_SIZE_GREATER_BUFFER_SIZE) \
+    _SFETCH_XMACRO(REQUEST_USERDATA_PTR_IS_SET_BUT_USERDATA_SIZE_IS_NULL) \
+    _SFETCH_XMACRO(REQUEST_USERDATA_PTR_IS_NULL_BUT_USERDATA_SIZE_IS_NOT) \
+    _SFETCH_XMACRO(REQUEST_USERDATA_SIZE_TOO_BIG) \
+    _SFETCH_XMACRO(CLAMPING_NUM_CHANNELS_TO_MAX_CHANNELS) \
+    _SFETCH_XMACRO(REQUEST_POOL_FULL) \
+
+#define _SFETCH_XMACRO(item) SFETCH_LOGITEM_##item,
+typedef enum sfetch_log_item_t {
+    _SFETCH_LOG_ITEMS
+} sfetch_log_item_t;
+#undef _SFETCH_XMACRO
+
+/*
+    sfetch_logger_t
+
+    Used in sfetch_desc_t to provide a custom logging and error reporting
+    callback to sokol-fetch.
+*/
+typedef struct sfetch_logger_t {
+    void (*func)(
+        const char* tag,                // always "sfetch"
+        uint32_t log_level,             // 0=panic, 1=error, 2=warning, 3=info
+        uint32_t log_item_id,           // SFETCH_LOGITEM_*
+        const char* message_or_null,    // a message string, may be nullptr in release mode
+        uint32_t line_nr,               // line number in sokol_fetch.h
+        const char* filename_or_null,   // source filename, may be nullptr in release mode
+        void* user_data);
+    void* user_data;
+} sfetch_logger_t;
+
 /*
 /*
     sfetch_range_t
     sfetch_range_t
 
 
@@ -958,24 +1029,13 @@ typedef struct sfetch_allocator_t {
     void* user_data;
     void* user_data;
 } sfetch_allocator_t;
 } sfetch_allocator_t;
 
 
-/*
-    sfetch_logger_t
-
-    Used in sfetch_desc_t to provide custom log callbacks to sokol_fetch.h.
-    Default behavior is SOKOL_LOG(message).
-*/
-typedef struct sfetch_logger_t {
-    void (*log_cb)(const char* message, void* user_data);
-    void* user_data;
-} sfetch_logger_t;
-
 /* configuration values for sfetch_setup() */
 /* configuration values for sfetch_setup() */
 typedef struct sfetch_desc_t {
 typedef struct sfetch_desc_t {
     uint32_t max_requests;          // max number of active requests across all channels (default: 128)
     uint32_t max_requests;          // max number of active requests across all channels (default: 128)
     uint32_t num_channels;          // number of channels to fetch requests in parallel (default: 1)
     uint32_t num_channels;          // number of channels to fetch requests in parallel (default: 1)
     uint32_t num_lanes;             // max number of requests active on the same channel (default: 1)
     uint32_t num_lanes;             // max number of requests active on the same channel (default: 1)
     sfetch_allocator_t allocator;   // optional memory allocation overrides (default: malloc/free)
     sfetch_allocator_t allocator;   // optional memory allocation overrides (default: malloc/free)
-    sfetch_logger_t logger;         // optional log function overrides (default: SOKOL_LOG(message))
+    sfetch_logger_t logger;         // optional log function overrides (default: NO LOGGING!)
 } sfetch_desc_t;
 } sfetch_desc_t;
 
 
 /* a request handle to identify an active fetch request, returned by sfetch_send() */
 /* a request handle to identify an active fetch request, returned by sfetch_send() */
@@ -1065,7 +1125,14 @@ inline sfetch_handle_t sfetch_send(const sfetch_request_t& request) { return sfe
 #endif
 #endif
 #endif // SOKOL_FETCH_INCLUDED
 #endif // SOKOL_FETCH_INCLUDED
 
 
-/*--- IMPLEMENTATION ---------------------------------------------------------*/
+// ██╗███╗   ███╗██████╗ ██╗     ███████╗███╗   ███╗███████╗███╗   ██╗████████╗ █████╗ ████████╗██╗ ██████╗ ███╗   ██╗
+// ██║████╗ ████║██╔══██╗██║     ██╔════╝████╗ ████║██╔════╝████╗  ██║╚══██╔══╝██╔══██╗╚══██╔══╝██║██╔═══██╗████╗  ██║
+// ██║██╔████╔██║██████╔╝██║     █████╗  ██╔████╔██║█████╗  ██╔██╗ ██║   ██║   ███████║   ██║   ██║██║   ██║██╔██╗ ██║
+// ██║██║╚██╔╝██║██╔═══╝ ██║     ██╔══╝  ██║╚██╔╝██║██╔══╝  ██║╚██╗██║   ██║   ██╔══██║   ██║   ██║██║   ██║██║╚██╗██║
+// ██║██║ ╚═╝ ██║██║     ███████╗███████╗██║ ╚═╝ ██║███████╗██║ ╚████║   ██║   ██║  ██║   ██║   ██║╚██████╔╝██║ ╚████║
+// ╚═╝╚═╝     ╚═╝╚═╝     ╚══════╝╚══════╝╚═╝     ╚═╝╚══════╝╚═╝  ╚═══╝   ╚═╝   ╚═╝  ╚═╝   ╚═╝   ╚═╝ ╚═════╝ ╚═╝  ╚═══╝
+//
+// >>implementation
 #ifdef SOKOL_FETCH_IMPL
 #ifdef SOKOL_FETCH_IMPL
 #define SOKOL_FETCH_IMPL_INCLUDED (1)
 #define SOKOL_FETCH_IMPL_INCLUDED (1)
 
 
@@ -1099,21 +1166,6 @@ inline sfetch_handle_t sfetch_send(const sfetch_request_t& request) { return sfe
     #define SOKOL_ASSERT(c) assert(c)
     #define SOKOL_ASSERT(c) assert(c)
 #endif
 #endif
 
 
-#if !defined(SOKOL_DEBUG)
-    #define SFETCH_LOG(s)
-#else
-    #define SFETCH_LOG(s) _sfetch_log(s)
-    #ifndef SOKOL_LOG
-        #if defined(__ANDROID__)
-            #include <android/log.h>
-            #define SOKOL_LOG(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_FETCH", s)
-        #else
-            #include <stdio.h>
-            #define SOKOL_LOG(s) puts(s)
-        #endif
-    #endif
-#endif
-
 #ifndef _SOKOL_PRIVATE
 #ifndef _SOKOL_PRIVATE
     #if defined(__GNUC__) || defined(__clang__)
     #if defined(__GNUC__) || defined(__clang__)
         #define _SOKOL_PRIVATE __attribute__((unused)) static
         #define _SOKOL_PRIVATE __attribute__((unused)) static
@@ -1153,7 +1205,14 @@ inline sfetch_handle_t sfetch_send(const sfetch_request_t& request) { return sfe
     #define _SFETCH_HAS_THREADS (1)
     #define _SFETCH_HAS_THREADS (1)
 #endif
 #endif
 
 
-/*=== private type definitions ===============================================*/
+// ███████╗████████╗██████╗ ██╗   ██╗ ██████╗████████╗███████╗
+// ██╔════╝╚══██╔══╝██╔══██╗██║   ██║██╔════╝╚══██╔══╝██╔════╝
+// ███████╗   ██║   ██████╔╝██║   ██║██║        ██║   ███████╗
+// ╚════██║   ██║   ██╔══██╗██║   ██║██║        ██║   ╚════██║
+// ███████║   ██║   ██║  ██║╚██████╔╝╚██████╗   ██║   ███████║
+// ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝  ╚═════╝   ╚═╝   ╚══════╝
+//
+// >>structs
 typedef struct _sfetch_path_t {
 typedef struct _sfetch_path_t {
     char buf[SFETCH_MAX_PATH];
     char buf[SFETCH_MAX_PATH];
 } _sfetch_path_t;
 } _sfetch_path_t;
@@ -1312,7 +1371,54 @@ static __thread _sfetch_t* _sfetch;
 static _sfetch_t* _sfetch;
 static _sfetch_t* _sfetch;
 #endif
 #endif
 
 
-/*=== general helper functions and macros =====================================*/
+// ██╗      ██████╗  ██████╗  ██████╗ ██╗███╗   ██╗ ██████╗
+// ██║     ██╔═══██╗██╔════╝ ██╔════╝ ██║████╗  ██║██╔════╝
+// ██║     ██║   ██║██║  ███╗██║  ███╗██║██╔██╗ ██║██║  ███╗
+// ██║     ██║   ██║██║   ██║██║   ██║██║██║╚██╗██║██║   ██║
+// ███████╗╚██████╔╝╚██████╔╝╚██████╔╝██║██║ ╚████║╚██████╔╝
+// ╚══════╝ ╚═════╝  ╚═════╝  ╚═════╝ ╚═╝╚═╝  ╚═══╝ ╚═════╝
+//
+// >>logging
+#if defined(SOKOL_DEBUG)
+#define _SFETCH_XMACRO(item) #item,
+static const char* _sfetch_log_messages[] = {
+    _SFETCH_LOG_ITEMS
+};
+#undef _SFETCH_XMACRO
+#endif // SOKOL_DEBUG
+
+#define _SFETCH_PANIC(code) _sfetch_log(SFETCH_LOGITEM_ ##code, 0, __LINE__)
+#define _SFETCH_ERROR(code) _sfetch_log(SFETCH_LOGITEM_ ##code, 1, __LINE__)
+#define _SFETCH_WARN(code) _sfetch_log(SFETCH_LOGITEM_ ##code, 2, __LINE__)
+#define _SFETCH_INFO(code) _sfetch_log(SFETCH_LOGITEM_ ##code, 3, __LINE__)
+
+static void _sfetch_log(sfetch_log_item_t log_item, uint32_t log_level, uint32_t line_nr) {
+    if (_sfetch->desc.logger.func) {
+//        #if defined(SOKOL_DEBUG)
+//            const char* filename = __FILE__;
+//            const char* message = _sfetch_log_messages[log_item];
+//        #else
+            const char* filename = 0;
+            const char* message = 0;
+//        #endif
+        _sfetch->desc.logger.func("sfetch", log_level, log_item, message, line_nr, filename, _sfetch->desc.logger.user_data);
+    }
+    else {
+        // for log level PANIC it would be 'undefined behaviour' to continue
+        if (log_level == 0) {
+            abort();
+        }
+    }
+}
+
+// ███╗   ███╗███████╗███╗   ███╗ ██████╗ ██████╗ ██╗   ██╗
+// ████╗ ████║██╔════╝████╗ ████║██╔═══██╗██╔══██╗╚██╗ ██╔╝
+// ██╔████╔██║█████╗  ██╔████╔██║██║   ██║██████╔╝ ╚████╔╝
+// ██║╚██╔╝██║██╔══╝  ██║╚██╔╝██║██║   ██║██╔══██╗  ╚██╔╝
+// ██║ ╚═╝ ██║███████╗██║ ╚═╝ ██║╚██████╔╝██║  ██║   ██║
+// ╚═╝     ╚═╝╚══════╝╚═╝     ╚═╝ ╚═════╝ ╚═╝  ╚═╝   ╚═╝
+//
+// >>memory
 #define _sfetch_def(val, def) (((val) == 0) ? (def) : (val))
 #define _sfetch_def(val, def) (((val) == 0) ? (def) : (val))
 
 
 _SOKOL_PRIVATE void _sfetch_clear(void* ptr, size_t size) {
 _SOKOL_PRIVATE void _sfetch_clear(void* ptr, size_t size) {
@@ -1329,7 +1435,9 @@ _SOKOL_PRIVATE void* _sfetch_malloc_with_allocator(const sfetch_allocator_t* all
     else {
     else {
         ptr = malloc(size);
         ptr = malloc(size);
     }
     }
-    SOKOL_ASSERT(ptr);
+    if (0 == ptr) {
+        _SFETCH_PANIC(MALLOC_FAILED);
+    }
     return ptr;
     return ptr;
 }
 }
 
 
@@ -1352,17 +1460,6 @@ _SOKOL_PRIVATE void _sfetch_free(void* ptr) {
     }
     }
 }
 }
 
 
-#if defined(SOKOL_DEBUG)
-_SOKOL_PRIVATE void _sfetch_log(const char* msg) {
-    if (_sfetch->desc.logger.log_cb) {
-        _sfetch->desc.logger.log_cb(msg, _sfetch->desc.logger.user_data);
-    }
-    else {
-        SOKOL_LOG(msg);
-    }
-}
-#endif
-
 _SOKOL_PRIVATE _sfetch_t* _sfetch_ctx(void) {
 _SOKOL_PRIVATE _sfetch_t* _sfetch_ctx(void) {
     return _sfetch;
     return _sfetch;
 }
 }
@@ -1388,21 +1485,14 @@ _SOKOL_PRIVATE _sfetch_path_t _sfetch_path_make(const char* str) {
     return res;
     return res;
 }
 }
 
 
-_SOKOL_PRIVATE uint32_t _sfetch_make_id(uint32_t index, uint32_t gen_ctr) {
-    return (gen_ctr<<16) | (index & 0xFFFF);
-}
-
-_SOKOL_PRIVATE sfetch_handle_t _sfetch_make_handle(uint32_t slot_id) {
-    sfetch_handle_t h;
-    h.id = slot_id;
-    return h;
-}
-
-_SOKOL_PRIVATE uint32_t _sfetch_slot_index(uint32_t slot_id) {
-    return slot_id & 0xFFFF;
-}
-
-/*=== a circular message queue ===============================================*/
+// ███╗   ███╗███████╗███████╗███████╗ █████╗  ██████╗ ███████╗     ██████╗ ██╗   ██╗███████╗██╗   ██╗███████╗
+// ████╗ ████║██╔════╝██╔════╝██╔════╝██╔══██╗██╔════╝ ██╔════╝    ██╔═══██╗██║   ██║██╔════╝██║   ██║██╔════╝
+// ██╔████╔██║█████╗  ███████╗███████╗███████║██║  ███╗█████╗      ██║   ██║██║   ██║█████╗  ██║   ██║█████╗
+// ██║╚██╔╝██║██╔══╝  ╚════██║╚════██║██╔══██║██║   ██║██╔══╝      ██║▄▄ ██║██║   ██║██╔══╝  ██║   ██║██╔══╝
+// ██║ ╚═╝ ██║███████╗███████║███████║██║  ██║╚██████╔╝███████╗    ╚██████╔╝╚██████╔╝███████╗╚██████╔╝███████╗
+// ╚═╝     ╚═╝╚══════╝╚══════╝╚══════╝╚═╝  ╚═╝ ╚═════╝ ╚══════╝     ╚══▀▀═╝  ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝
+//
+// >>message queue
 _SOKOL_PRIVATE uint32_t _sfetch_ring_wrap(const _sfetch_ring_t* rb, uint32_t i) {
 _SOKOL_PRIVATE uint32_t _sfetch_ring_wrap(const _sfetch_ring_t* rb, uint32_t i) {
     return i % rb->num;
     return i % rb->num;
 }
 }
@@ -1484,7 +1574,28 @@ _SOKOL_PRIVATE uint32_t _sfetch_ring_peek(const _sfetch_ring_t* rb, uint32_t ind
     return rb->buf[rb_index];
     return rb->buf[rb_index];
 }
 }
 
 
-/*=== request pool implementation ============================================*/
+// ██████╗ ███████╗ ██████╗ ██╗   ██╗███████╗███████╗████████╗    ██████╗  ██████╗  ██████╗ ██╗
+// ██╔══██╗██╔════╝██╔═══██╗██║   ██║██╔════╝██╔════╝╚══██╔══╝    ██╔══██╗██╔═══██╗██╔═══██╗██║
+// ██████╔╝█████╗  ██║   ██║██║   ██║█████╗  ███████╗   ██║       ██████╔╝██║   ██║██║   ██║██║
+// ██╔══██╗██╔══╝  ██║▄▄ ██║██║   ██║██╔══╝  ╚════██║   ██║       ██╔═══╝ ██║   ██║██║   ██║██║
+// ██║  ██║███████╗╚██████╔╝╚██████╔╝███████╗███████║   ██║       ██║     ╚██████╔╝╚██████╔╝███████╗
+// ╚═╝  ╚═╝╚══════╝ ╚══▀▀═╝  ╚═════╝ ╚══════╝╚══════╝   ╚═╝       ╚═╝      ╚═════╝  ╚═════╝ ╚══════╝
+//
+// >>request pool
+_SOKOL_PRIVATE uint32_t _sfetch_make_id(uint32_t index, uint32_t gen_ctr) {
+    return (gen_ctr<<16) | (index & 0xFFFF);
+}
+
+_SOKOL_PRIVATE sfetch_handle_t _sfetch_make_handle(uint32_t slot_id) {
+    sfetch_handle_t h;
+    h.id = slot_id;
+    return h;
+}
+
+_SOKOL_PRIVATE uint32_t _sfetch_slot_index(uint32_t slot_id) {
+    return slot_id & 0xFFFF;
+}
+
 _SOKOL_PRIVATE void _sfetch_item_init(_sfetch_item_t* item, uint32_t slot_id, const sfetch_request_t* request) {
 _SOKOL_PRIVATE void _sfetch_item_init(_sfetch_item_t* item, uint32_t slot_id, const sfetch_request_t* request) {
     SOKOL_ASSERT(item && (0 == item->handle.id));
     SOKOL_ASSERT(item && (0 == item->handle.id));
     SOKOL_ASSERT(request && request->path);
     SOKOL_ASSERT(request && request->path);
@@ -1614,7 +1725,14 @@ _SOKOL_PRIVATE _sfetch_item_t* _sfetch_pool_item_lookup(_sfetch_pool_t* pool, ui
     return 0;
     return 0;
 }
 }
 
 
-/*=== PLATFORM WRAPPER FUNCTIONS =============================================*/
+// ██████╗  ██████╗ ███████╗██╗██╗  ██╗
+// ██╔══██╗██╔═══██╗██╔════╝██║╚██╗██╔╝
+// ██████╔╝██║   ██║███████╗██║ ╚███╔╝
+// ██╔═══╝ ██║   ██║╚════██║██║ ██╔██╗
+// ██║     ╚██████╔╝███████║██║██╔╝ ██╗
+// ╚═╝      ╚═════╝ ╚══════╝╚═╝╚═╝  ╚═╝
+//
+// >>posix
 #if _SFETCH_PLATFORM_POSIX
 #if _SFETCH_PLATFORM_POSIX
 _SOKOL_PRIVATE _sfetch_file_handle_t _sfetch_file_open(const _sfetch_path_t* path) {
 _SOKOL_PRIVATE _sfetch_file_handle_t _sfetch_file_open(const _sfetch_path_t* path) {
     return fopen(path->buf, "rb");
     return fopen(path->buf, "rb");
@@ -1771,6 +1889,14 @@ _SOKOL_PRIVATE void _sfetch_thread_dequeue_outgoing(_sfetch_thread_t* thread, _s
 }
 }
 #endif /* _SFETCH_PLATFORM_POSIX */
 #endif /* _SFETCH_PLATFORM_POSIX */
 
 
+// ██╗    ██╗██╗███╗   ██╗██████╗  ██████╗ ██╗    ██╗███████╗
+// ██║    ██║██║████╗  ██║██╔══██╗██╔═══██╗██║    ██║██╔════╝
+// ██║ █╗ ██║██║██╔██╗ ██║██║  ██║██║   ██║██║ █╗ ██║███████╗
+// ██║███╗██║██║██║╚██╗██║██║  ██║██║   ██║██║███╗██║╚════██║
+// ╚███╔███╔╝██║██║ ╚████║██████╔╝╚██████╔╝╚███╔███╔╝███████║
+//  ╚══╝╚══╝ ╚═╝╚═╝  ╚═══╝╚═════╝  ╚═════╝  ╚══╝╚══╝ ╚══════╝
+//
+// >>windows
 #if _SFETCH_PLATFORM_WINDOWS
 #if _SFETCH_PLATFORM_WINDOWS
 _SOKOL_PRIVATE bool _sfetch_win32_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) {
 _SOKOL_PRIVATE bool _sfetch_win32_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) {
     SOKOL_ASSERT(src && dst && (dst_num_bytes > 1));
     SOKOL_ASSERT(src && dst && (dst_num_bytes > 1));
@@ -1790,7 +1916,7 @@ _SOKOL_PRIVATE bool _sfetch_win32_utf8_to_wide(const char* src, wchar_t* dst, in
 _SOKOL_PRIVATE _sfetch_file_handle_t _sfetch_file_open(const _sfetch_path_t* path) {
 _SOKOL_PRIVATE _sfetch_file_handle_t _sfetch_file_open(const _sfetch_path_t* path) {
     wchar_t w_path[SFETCH_MAX_PATH];
     wchar_t w_path[SFETCH_MAX_PATH];
     if (!_sfetch_win32_utf8_to_wide(path->buf, w_path, sizeof(w_path))) {
     if (!_sfetch_win32_utf8_to_wide(path->buf, w_path, sizeof(w_path))) {
-        SFETCH_LOG("_sfetch_file_open: error converting UTF-8 path to wide string");
+        _SFETCH_ERROR(FILE_PATH_UTF8_CONVERSION_FAILED);
         return 0;
         return 0;
     }
     }
     _sfetch_file_handle_t h = CreateFileW(
     _sfetch_file_handle_t h = CreateFileW(
@@ -1950,7 +2076,14 @@ _SOKOL_PRIVATE void _sfetch_thread_dequeue_outgoing(_sfetch_thread_t* thread, _s
 }
 }
 #endif /* _SFETCH_PLATFORM_WINDOWS */
 #endif /* _SFETCH_PLATFORM_WINDOWS */
 
 
-/*=== IO CHANNEL implementation ==============================================*/
+//  ██████╗██╗  ██╗ █████╗ ███╗   ██╗███╗   ██╗███████╗██╗     ███████╗
+// ██╔════╝██║  ██║██╔══██╗████╗  ██║████╗  ██║██╔════╝██║     ██╔════╝
+// ██║     ███████║███████║██╔██╗ ██║██╔██╗ ██║█████╗  ██║     ███████╗
+// ██║     ██╔══██║██╔══██║██║╚██╗██║██║╚██╗██║██╔══╝  ██║     ╚════██║
+// ╚██████╗██║  ██║██║  ██║██║ ╚████║██║ ╚████║███████╗███████╗███████║
+//  ╚═════╝╚═╝  ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝╚═╝  ╚═══╝╚══════╝╚══════╝╚══════╝
+//
+// >>channels
 
 
 /* per-channel request handler for native platforms accessing the local filesystem */
 /* per-channel request handler for native platforms accessing the local filesystem */
 #if _SFETCH_HAS_THREADS
 #if _SFETCH_HAS_THREADS
@@ -2074,7 +2207,6 @@ _SOKOL_PRIVATE void* _sfetch_channel_thread_func(void* arg) {
 #endif /* _SFETCH_HAS_THREADS */
 #endif /* _SFETCH_HAS_THREADS */
 
 
 #if _SFETCH_PLATFORM_EMSCRIPTEN
 #if _SFETCH_PLATFORM_EMSCRIPTEN
-/*=== embedded Javascript helper functions ===================================*/
 EM_JS(void, sfetch_js_send_head_request, (uint32_t slot_id, const char* path_cstr), {
 EM_JS(void, sfetch_js_send_head_request, (uint32_t slot_id, const char* path_cstr), {
     const path_str = UTF8ToString(path_cstr);
     const path_str = UTF8ToString(path_cstr);
     const req = new XMLHttpRequest();
     const req = new XMLHttpRequest();
@@ -2308,7 +2440,7 @@ _SOKOL_PRIVATE bool _sfetch_channel_send(_sfetch_channel_t* chn, uint32_t slot_i
         return true;
         return true;
     }
     }
     else {
     else {
-        SFETCH_LOG("sfetch_send: user_sent queue is full)");
+        _SFETCH_ERROR(SEND_QUEUE_FULL);
         return false;
         return false;
     }
     }
 }
 }
@@ -2449,45 +2581,39 @@ _SOKOL_PRIVATE void _sfetch_channel_dowork(_sfetch_channel_t* chn, _sfetch_pool_
     }
     }
 }
 }
 
 
-/*=== private high-level functions ===========================================*/
 _SOKOL_PRIVATE bool _sfetch_validate_request(_sfetch_t* ctx, const sfetch_request_t* req) {
 _SOKOL_PRIVATE bool _sfetch_validate_request(_sfetch_t* ctx, const sfetch_request_t* req) {
-    #if defined(SOKOL_DEBUG)
-        if (req->channel >= ctx->desc.num_channels) {
-            SFETCH_LOG("_sfetch_validate_request: request.channel too big!");
-            return false;
-        }
-        if (!req->path) {
-            SFETCH_LOG("_sfetch_validate_request: request.path is null!");
-            return false;
-        }
-        if (strlen(req->path) >= (SFETCH_MAX_PATH-1)) {
-            SFETCH_LOG("_sfetch_validate_request: request.path is too long (must be < SFETCH_MAX_PATH-1)");
-            return false;
-        }
-        if (!req->callback) {
-            SFETCH_LOG("_sfetch_validate_request: request.callback missing");
-            return false;
-        }
-        if (req->chunk_size > req->buffer.size) {
-            SFETCH_LOG("_sfetch_validate_request: request.chunk_size is greater request.buffer.size)");
-            return false;
-        }
-        if (req->user_data.ptr && (req->user_data.size == 0)) {
-            SFETCH_LOG("_sfetch_validate_request: request.user_data.ptr is set, but request.user_data.size is null");
-            return false;
-        }
-        if (!req->user_data.ptr && (req->user_data.size > 0)) {
-            SFETCH_LOG("_sfetch_validate_request: request.user_data.ptr is null, but request.user_data.size is not");
-            return false;
-        }
-        if (req->user_data.size > SFETCH_MAX_USERDATA_UINT64 * sizeof(uint64_t)) {
-            SFETCH_LOG("_sfetch_validate_request: request.user_data.size is too big (see SFETCH_MAX_USERDATA_UINT64");
-            return false;
-        }
-    #else
-        /* silence unused warnings in release*/
-        (void)(ctx && req);
-    #endif
+    if (req->channel >= ctx->desc.num_channels) {
+        _SFETCH_ERROR(REQUEST_CHANNEL_TOO_BIG);
+        return false;
+    }
+    if (!req->path) {
+        _SFETCH_ERROR(REQUEST_PATH_IS_NULL);
+        return false;
+    }
+    if (strlen(req->path) >= (SFETCH_MAX_PATH-1)) {
+        _SFETCH_ERROR(REQUEST_PATH_TOO_LONG);
+        return false;
+    }
+    if (!req->callback) {
+        _SFETCH_ERROR(REQUEST_CALLBACK_MISSING);
+        return false;
+    }
+    if (req->chunk_size > req->buffer.size) {
+        _SFETCH_ERROR(REQUEST_CHUNK_SIZE_GREATER_BUFFER_SIZE);
+        return false;
+    }
+    if (req->user_data.ptr && (req->user_data.size == 0)) {
+        _SFETCH_ERROR(REQUEST_USERDATA_PTR_IS_SET_BUT_USERDATA_SIZE_IS_NULL);
+        return false;
+    }
+    if (!req->user_data.ptr && (req->user_data.size > 0)) {
+        _SFETCH_ERROR(REQUEST_USERDATA_PTR_IS_NULL_BUT_USERDATA_SIZE_IS_NOT);
+        return false;
+    }
+    if (req->user_data.size > SFETCH_MAX_USERDATA_UINT64 * sizeof(uint64_t)) {
+        _SFETCH_ERROR(REQUEST_USERDATA_SIZE_TOO_BIG);
+        return false;
+    }
     return true;
     return true;
 }
 }
 
 
@@ -2500,7 +2626,14 @@ _SOKOL_PRIVATE sfetch_desc_t _sfetch_desc_defaults(const sfetch_desc_t* desc) {
     return res;
     return res;
 }
 }
 
 
-/*=== PUBLIC API FUNCTIONS ===================================================*/
+// ██████╗ ██╗   ██╗██████╗ ██╗     ██╗ ██████╗
+// ██╔══██╗██║   ██║██╔══██╗██║     ██║██╔════╝
+// ██████╔╝██║   ██║██████╔╝██║     ██║██║
+// ██╔═══╝ ██║   ██║██╔══██╗██║     ██║██║
+// ██║     ╚██████╔╝██████╔╝███████╗██║╚██████╗
+// ╚═╝      ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝
+//
+// >>public
 SOKOL_API_IMPL void sfetch_setup(const sfetch_desc_t* desc_) {
 SOKOL_API_IMPL void sfetch_setup(const sfetch_desc_t* desc_) {
     SOKOL_ASSERT(desc_);
     SOKOL_ASSERT(desc_);
     SOKOL_ASSERT(0 == _sfetch);
     SOKOL_ASSERT(0 == _sfetch);
@@ -2517,7 +2650,7 @@ SOKOL_API_IMPL void sfetch_setup(const sfetch_desc_t* desc_) {
     /* replace zero-init items with default values */
     /* replace zero-init items with default values */
     if (ctx->desc.num_channels > SFETCH_MAX_CHANNELS) {
     if (ctx->desc.num_channels > SFETCH_MAX_CHANNELS) {
         ctx->desc.num_channels = SFETCH_MAX_CHANNELS;
         ctx->desc.num_channels = SFETCH_MAX_CHANNELS;
-        SFETCH_LOG("sfetch_setup: clamping num_channels to SFETCH_MAX_CHANNELS");
+        _SFETCH_WARN(CLAMPING_NUM_CHANNELS_TO_MAX_CHANNELS);
     }
     }
 
 
     /* setup the global request item pool */
     /* setup the global request item pool */
@@ -2589,7 +2722,7 @@ SOKOL_API_IMPL sfetch_handle_t sfetch_send(const sfetch_request_t* request) {
 
 
     uint32_t slot_id = _sfetch_pool_item_alloc(&ctx->pool, request);
     uint32_t slot_id = _sfetch_pool_item_alloc(&ctx->pool, request);
     if (0 == slot_id) {
     if (0 == slot_id) {
-        SFETCH_LOG("sfetch_send: request pool exhausted (too many active requests)");
+        _SFETCH_WARN(REQUEST_POOL_FULL);
         return invalid_handle;
         return invalid_handle;
     }
     }
     if (!_sfetch_channel_send(&ctx->chn[request->channel], slot_id)) {
     if (!_sfetch_channel_send(&ctx->chn[request->channel], slot_id)) {