#if defined(SOKOL_IMPL) && !defined(SOKOL_FETCH_IMPL)
#define SOKOL_FETCH_IMPL
#endif
#ifndef SOKOL_FETCH_INCLUDED
#define SOKOL_FETCH_INCLUDED (1)
#include <stdint.h>
#include <stdbool.h>
#if defined(SOKOL_API_DECL) && !defined(SOKOL_FETCH_API_DECL)
#define SOKOL_FETCH_API_DECL SOKOL_API_DECL
#endif
#ifndef SOKOL_FETCH_API_DECL
#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_FETCH_IMPL)
#define SOKOL_FETCH_API_DECL __declspec(dllexport)
#elif defined(_WIN32) && defined(SOKOL_DLL)
#define SOKOL_FETCH_API_DECL __declspec(dllimport)
#else
#define SOKOL_FETCH_API_DECL extern
#endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef struct sfetch_desc_t {
uint32_t _start_canary;
uint32_t max_requests;
uint32_t num_channels;
uint32_t num_lanes;
uint32_t _end_canary;
} sfetch_desc_t;
typedef struct sfetch_handle_t { uint32_t id; } sfetch_handle_t;
typedef enum sfetch_error_t {
SFETCH_ERROR_NO_ERROR,
SFETCH_ERROR_FILE_NOT_FOUND,
SFETCH_ERROR_NO_BUFFER,
SFETCH_ERROR_BUFFER_TOO_SMALL,
SFETCH_ERROR_UNEXPECTED_EOF,
SFETCH_ERROR_INVALID_HTTP_STATUS,
SFETCH_ERROR_CANCELLED
} sfetch_error_t;
typedef struct sfetch_response_t {
sfetch_handle_t handle;
bool dispatched;
bool fetched;
bool paused;
bool finished;
bool failed;
bool cancelled;
sfetch_error_t error_code;
uint32_t channel;
uint32_t lane;
const char* path;
void* user_data;
uint32_t fetched_offset;
uint32_t fetched_size;
void* buffer_ptr;
uint32_t buffer_size;
} sfetch_response_t;
typedef void(*sfetch_callback_t)(const sfetch_response_t*);
typedef struct sfetch_request_t {
uint32_t _start_canary;
uint32_t channel;
const char* path;
sfetch_callback_t callback;
void* buffer_ptr;
uint32_t buffer_size;
uint32_t chunk_size;
const void* user_data_ptr;
uint32_t user_data_size;
uint32_t _end_canary;
} sfetch_request_t;
SOKOL_FETCH_API_DECL void sfetch_setup(const sfetch_desc_t* desc);
SOKOL_FETCH_API_DECL void sfetch_shutdown(void);
SOKOL_FETCH_API_DECL bool sfetch_valid(void);
SOKOL_FETCH_API_DECL sfetch_desc_t sfetch_desc(void);
SOKOL_FETCH_API_DECL int sfetch_max_userdata_bytes(void);
SOKOL_FETCH_API_DECL int sfetch_max_path(void);
SOKOL_FETCH_API_DECL sfetch_handle_t sfetch_send(const sfetch_request_t* request);
SOKOL_FETCH_API_DECL bool sfetch_handle_valid(sfetch_handle_t h);
SOKOL_FETCH_API_DECL void sfetch_dowork(void);
SOKOL_FETCH_API_DECL void sfetch_bind_buffer(sfetch_handle_t h, void* buffer_ptr, uint32_t buffer_size);
SOKOL_FETCH_API_DECL void* sfetch_unbind_buffer(sfetch_handle_t h);
SOKOL_FETCH_API_DECL void sfetch_cancel(sfetch_handle_t h);
SOKOL_FETCH_API_DECL void sfetch_pause(sfetch_handle_t h);
SOKOL_FETCH_API_DECL void sfetch_continue(sfetch_handle_t h);
#ifdef __cplusplus
}
inline void sfetch_setup(const sfetch_desc_t& desc) { return sfetch_setup(&desc); }
inline sfetch_handle_t sfetch_send(const sfetch_request_t& request) { return sfetch_send(&request); }
#endif
#endif
#ifdef SOKOL_FETCH_IMPL
#define SOKOL_FETCH_IMPL_INCLUDED (1)
#include <string.h>
#ifndef SFETCH_MAX_PATH
#define SFETCH_MAX_PATH (1024)
#endif
#ifndef SFETCH_MAX_USERDATA_UINT64
#define SFETCH_MAX_USERDATA_UINT64 (16)
#endif
#ifndef SFETCH_MAX_CHANNELS
#define SFETCH_MAX_CHANNELS (16)
#endif
#ifndef SOKOL_API_IMPL
#define SOKOL_API_IMPL
#endif
#ifndef SOKOL_DEBUG
#ifndef NDEBUG
#define SOKOL_DEBUG (1)
#endif
#endif
#ifndef SOKOL_ASSERT
#include <assert.h>
#define SOKOL_ASSERT(c) assert(c)
#endif
#ifndef SOKOL_MALLOC
#include <stdlib.h>
#define SOKOL_MALLOC(s) malloc(s)
#define SOKOL_FREE(p) free(p)
#endif
#ifndef SOKOL_LOG
#ifdef SOKOL_DEBUG
#include <stdio.h>
#define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
#else
#define SOKOL_LOG(s)
#endif
#endif
#ifndef _SOKOL_PRIVATE
#if defined(__GNUC__) || defined(__clang__)
#define _SOKOL_PRIVATE __attribute__((unused)) static
#else
#define _SOKOL_PRIVATE static
#endif
#endif
#ifndef _SOKOL_UNUSED
#define _SOKOL_UNUSED(x) (void)(x)
#endif
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#define _SFETCH_PLATFORM_EMSCRIPTEN (1)
#define _SFETCH_PLATFORM_WINDOWS (0)
#define _SFETCH_PLATFORM_POSIX (0)
#define _SFETCH_HAS_THREADS (0)
#elif defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#define _SFETCH_PLATFORM_WINDOWS (1)
#define _SFETCH_PLATFORM_EMSCRIPTEN (0)
#define _SFETCH_PLATFORM_POSIX (0)
#define _SFETCH_HAS_THREADS (1)
#else
#include <pthread.h>
#include <stdio.h>
#define _SFETCH_PLATFORM_POSIX (1)
#define _SFETCH_PLATFORM_EMSCRIPTEN (0)
#define _SFETCH_PLATFORM_WINDOWS (0)
#define _SFETCH_HAS_THREADS (1)
#endif
typedef struct _sfetch_path_t {
char buf[SFETCH_MAX_PATH];
} _sfetch_path_t;
typedef struct _sfetch_buffer_t {
uint8_t* ptr;
uint32_t size;
} _sfetch_buffer_t;
#if _SFETCH_PLATFORM_POSIX
typedef struct {
pthread_t thread;
pthread_cond_t incoming_cond;
pthread_mutex_t incoming_mutex;
pthread_mutex_t outgoing_mutex;
pthread_mutex_t running_mutex;
pthread_mutex_t stop_mutex;
bool stop_requested;
bool valid;
} _sfetch_thread_t;
#elif _SFETCH_PLATFORM_WINDOWS
typedef struct {
HANDLE thread;
HANDLE incoming_event;
CRITICAL_SECTION incoming_critsec;
CRITICAL_SECTION outgoing_critsec;
CRITICAL_SECTION running_critsec;
CRITICAL_SECTION stop_critsec;
bool stop_requested;
bool valid;
} _sfetch_thread_t;
#endif
#if _SFETCH_PLATFORM_POSIX
typedef FILE* _sfetch_file_handle_t;
#define _SFETCH_INVALID_FILE_HANDLE (0)
typedef void*(*_sfetch_thread_func_t)(void*);
#elif _SFETCH_PLATFORM_WINDOWS
typedef HANDLE _sfetch_file_handle_t;
#define _SFETCH_INVALID_FILE_HANDLE (INVALID_HANDLE_VALUE)
typedef LPTHREAD_START_ROUTINE _sfetch_thread_func_t;
#endif
typedef struct {
bool pause;
bool cont;
bool cancel;
uint32_t fetched_offset;
uint32_t fetched_size;
sfetch_error_t error_code;
bool finished;
uint32_t user_data_size;
uint64_t user_data[SFETCH_MAX_USERDATA_UINT64];
} _sfetch_item_user_t;
typedef struct {
uint32_t fetched_offset;
uint32_t fetched_size;
sfetch_error_t error_code;
bool failed;
bool finished;
#if _SFETCH_PLATFORM_EMSCRIPTEN
uint32_t http_range_offset;
#else
_sfetch_file_handle_t file_handle;
#endif
uint32_t content_size;
} _sfetch_item_thread_t;
typedef enum _sfetch_state_t {
_SFETCH_STATE_INITIAL,
_SFETCH_STATE_ALLOCATED,
_SFETCH_STATE_DISPATCHED,
_SFETCH_STATE_FETCHING,
_SFETCH_STATE_FETCHED,
_SFETCH_STATE_PAUSED,
_SFETCH_STATE_FAILED,
} _sfetch_state_t;
#define _SFETCH_INVALID_LANE (0xFFFFFFFF)
typedef struct {
sfetch_handle_t handle;
_sfetch_state_t state;
uint32_t channel;
uint32_t lane;
uint32_t chunk_size;
sfetch_callback_t callback;
_sfetch_buffer_t buffer;
_sfetch_item_thread_t thread;
_sfetch_item_user_t user;
_sfetch_path_t path;
} _sfetch_item_t;
typedef struct {
uint32_t size;
uint32_t free_top;
_sfetch_item_t* items;
uint32_t* free_slots;
uint32_t* gen_ctrs;
bool valid;
} _sfetch_pool_t;
typedef struct {
uint32_t head;
uint32_t tail;
uint32_t num;
uint32_t* buf;
} _sfetch_ring_t;
struct _sfetch_t;
typedef struct {
struct _sfetch_t* ctx;
_sfetch_ring_t free_lanes;
_sfetch_ring_t user_sent;
_sfetch_ring_t user_incoming;
_sfetch_ring_t user_outgoing;
#if _SFETCH_HAS_THREADS
_sfetch_ring_t thread_incoming;
_sfetch_ring_t thread_outgoing;
_sfetch_thread_t thread;
#endif
void (*request_handler)(struct _sfetch_t* ctx, uint32_t slot_id);
bool valid;
} _sfetch_channel_t;
typedef struct _sfetch_t {
bool setup;
bool valid;
bool in_callback;
sfetch_desc_t desc;
_sfetch_pool_t pool;
_sfetch_channel_t chn[SFETCH_MAX_CHANNELS];
} _sfetch_t;
#if _SFETCH_HAS_THREADS
#if defined(_MSC_VER)
static __declspec(thread) _sfetch_t* _sfetch;
#else
static __thread _sfetch_t* _sfetch;
#endif
#else
static _sfetch_t* _sfetch;
#endif
#define _sfetch_def(val, def) (((val) == 0) ? (def) : (val))
_SOKOL_PRIVATE _sfetch_t* _sfetch_ctx(void) {
return _sfetch;
}
_SOKOL_PRIVATE void _sfetch_path_copy(_sfetch_path_t* dst, const char* src) {
SOKOL_ASSERT(dst);
if (src && (strlen(src) < SFETCH_MAX_PATH)) {
#if defined(_MSC_VER)
strncpy_s(dst->buf, SFETCH_MAX_PATH, src, (SFETCH_MAX_PATH-1));
#else
strncpy(dst->buf, src, SFETCH_MAX_PATH);
#endif
dst->buf[SFETCH_MAX_PATH-1] = 0;
}
else {
memset(dst->buf, 0, SFETCH_MAX_PATH);
}
}
_SOKOL_PRIVATE _sfetch_path_t _sfetch_path_make(const char* str) {
_sfetch_path_t res;
_sfetch_path_copy(&res, str);
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;
}
_SOKOL_PRIVATE uint32_t _sfetch_ring_wrap(const _sfetch_ring_t* rb, uint32_t i) {
return i % rb->num;
}
_SOKOL_PRIVATE void _sfetch_ring_discard(_sfetch_ring_t* rb) {
SOKOL_ASSERT(rb);
if (rb->buf) {
SOKOL_FREE(rb->buf);
rb->buf = 0;
}
rb->head = 0;
rb->tail = 0;
rb->num = 0;
}
_SOKOL_PRIVATE bool _sfetch_ring_init(_sfetch_ring_t* rb, uint32_t num_slots) {
SOKOL_ASSERT(rb && (num_slots > 0));
SOKOL_ASSERT(0 == rb->buf);
rb->head = 0;
rb->tail = 0;
rb->num = num_slots + 1;
const size_t queue_size = rb->num * sizeof(sfetch_handle_t);
rb->buf = (uint32_t*) SOKOL_MALLOC(queue_size);
if (rb->buf) {
memset(rb->buf, 0, queue_size);
return true;
}
else {
_sfetch_ring_discard(rb);
return false;
}
}
_SOKOL_PRIVATE bool _sfetch_ring_full(const _sfetch_ring_t* rb) {
SOKOL_ASSERT(rb && rb->buf);
return _sfetch_ring_wrap(rb, rb->head + 1) == rb->tail;
}
_SOKOL_PRIVATE bool _sfetch_ring_empty(const _sfetch_ring_t* rb) {
SOKOL_ASSERT(rb && rb->buf);
return rb->head == rb->tail;
}
_SOKOL_PRIVATE uint32_t _sfetch_ring_count(const _sfetch_ring_t* rb) {
SOKOL_ASSERT(rb && rb->buf);
uint32_t count;
if (rb->head >= rb->tail) {
count = rb->head - rb->tail;
}
else {
count = (rb->head + rb->num) - rb->tail;
}
SOKOL_ASSERT(count < rb->num);
return count;
}
_SOKOL_PRIVATE void _sfetch_ring_enqueue(_sfetch_ring_t* rb, uint32_t slot_id) {
SOKOL_ASSERT(rb && rb->buf);
SOKOL_ASSERT(!_sfetch_ring_full(rb));
SOKOL_ASSERT(rb->head < rb->num);
rb->buf[rb->head] = slot_id;
rb->head = _sfetch_ring_wrap(rb, rb->head + 1);
}
_SOKOL_PRIVATE uint32_t _sfetch_ring_dequeue(_sfetch_ring_t* rb) {
SOKOL_ASSERT(rb && rb->buf);
SOKOL_ASSERT(!_sfetch_ring_empty(rb));
SOKOL_ASSERT(rb->tail < rb->num);
uint32_t slot_id = rb->buf[rb->tail];
rb->tail = _sfetch_ring_wrap(rb, rb->tail + 1);
return slot_id;
}
_SOKOL_PRIVATE uint32_t _sfetch_ring_peek(const _sfetch_ring_t* rb, uint32_t index) {
SOKOL_ASSERT(rb && rb->buf);
SOKOL_ASSERT(!_sfetch_ring_empty(rb));
SOKOL_ASSERT(index < _sfetch_ring_count(rb));
uint32_t rb_index = _sfetch_ring_wrap(rb, rb->tail + index);
return rb->buf[rb_index];
}
_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(request && request->path);
memset(item, 0, sizeof(_sfetch_item_t));
item->handle.id = slot_id;
item->state = _SFETCH_STATE_INITIAL;
item->channel = request->channel;
item->chunk_size = request->chunk_size;
item->lane = _SFETCH_INVALID_LANE;
item->callback = request->callback;
item->buffer.ptr = (uint8_t*) request->buffer_ptr;
item->buffer.size = request->buffer_size;
item->path = _sfetch_path_make(request->path);
#if !_SFETCH_PLATFORM_EMSCRIPTEN
item->thread.file_handle = _SFETCH_INVALID_FILE_HANDLE;
#endif
if (request->user_data_ptr &&
(request->user_data_size > 0) &&
(request->user_data_size <= (SFETCH_MAX_USERDATA_UINT64*8)))
{
item->user.user_data_size = request->user_data_size;
memcpy(item->user.user_data, request->user_data_ptr, request->user_data_size);
}
}
_SOKOL_PRIVATE void _sfetch_item_discard(_sfetch_item_t* item) {
SOKOL_ASSERT(item && (0 != item->handle.id));
memset(item, 0, sizeof(_sfetch_item_t));
}
_SOKOL_PRIVATE void _sfetch_pool_discard(_sfetch_pool_t* pool) {
SOKOL_ASSERT(pool);
if (pool->free_slots) {
SOKOL_FREE(pool->free_slots);
pool->free_slots = 0;
}
if (pool->gen_ctrs) {
SOKOL_FREE(pool->gen_ctrs);
pool->gen_ctrs = 0;
}
if (pool->items) {
SOKOL_FREE(pool->items);
pool->items = 0;
}
pool->size = 0;
pool->free_top = 0;
pool->valid = false;
}
_SOKOL_PRIVATE bool _sfetch_pool_init(_sfetch_pool_t* pool, uint32_t num_items) {
SOKOL_ASSERT(pool && (num_items > 0) && (num_items < ((1<<16)-1)));
SOKOL_ASSERT(0 == pool->items);
pool->size = num_items + 1;
pool->free_top = 0;
const size_t items_size = pool->size * sizeof(_sfetch_item_t);
pool->items = (_sfetch_item_t*) SOKOL_MALLOC(items_size);
const size_t gen_ctrs_size = sizeof(uint32_t) * pool->size;
pool->gen_ctrs = (uint32_t*) SOKOL_MALLOC(gen_ctrs_size);
SOKOL_ASSERT(pool->gen_ctrs);
const size_t free_slots_size = num_items * sizeof(int);
pool->free_slots = (uint32_t*) SOKOL_MALLOC(free_slots_size);
if (pool->items && pool->free_slots) {
memset(pool->items, 0, items_size);
memset(pool->gen_ctrs, 0, gen_ctrs_size);
for (uint32_t i = pool->size - 1; i >= 1; i--) {
pool->free_slots[pool->free_top++] = i;
}
pool->valid = true;
}
else {
_sfetch_pool_discard(pool);
}
return pool->valid;
}
_SOKOL_PRIVATE uint32_t _sfetch_pool_item_alloc(_sfetch_pool_t* pool, const sfetch_request_t* request) {
SOKOL_ASSERT(pool && pool->valid);
if (pool->free_top > 0) {
uint32_t slot_index = pool->free_slots[--pool->free_top];
SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size));
uint32_t slot_id = _sfetch_make_id(slot_index, ++pool->gen_ctrs[slot_index]);
_sfetch_item_init(&pool->items[slot_index], slot_id, request);
pool->items[slot_index].state = _SFETCH_STATE_ALLOCATED;
return slot_id;
}
else {
return _sfetch_make_id(0, 0);
}
}
_SOKOL_PRIVATE void _sfetch_pool_item_free(_sfetch_pool_t* pool, uint32_t slot_id) {
SOKOL_ASSERT(pool && pool->valid);
uint32_t slot_index = _sfetch_slot_index(slot_id);
SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size));
SOKOL_ASSERT(pool->items[slot_index].handle.id == slot_id);
#if defined(SOKOL_DEBUG)
for (uint32_t i = 0; i < pool->free_top; i++) {
SOKOL_ASSERT(pool->free_slots[i] != slot_index);
}
#endif
_sfetch_item_discard(&pool->items[slot_index]);
pool->free_slots[pool->free_top++] = slot_index;
SOKOL_ASSERT(pool->free_top <= (pool->size - 1));
}
_SOKOL_PRIVATE _sfetch_item_t* _sfetch_pool_item_at(_sfetch_pool_t* pool, uint32_t slot_id) {
SOKOL_ASSERT(pool && pool->valid);
uint32_t slot_index = _sfetch_slot_index(slot_id);
SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size));
return &pool->items[slot_index];
}
_SOKOL_PRIVATE _sfetch_item_t* _sfetch_pool_item_lookup(_sfetch_pool_t* pool, uint32_t slot_id) {
SOKOL_ASSERT(pool && pool->valid);
if (0 != slot_id) {
_sfetch_item_t* item = _sfetch_pool_item_at(pool, slot_id);
if (item->handle.id == slot_id) {
return item;
}
}
return 0;
}
#if _SFETCH_PLATFORM_POSIX
_SOKOL_PRIVATE _sfetch_file_handle_t _sfetch_file_open(const _sfetch_path_t* path) {
return fopen(path->buf, "rb");
}
_SOKOL_PRIVATE void _sfetch_file_close(_sfetch_file_handle_t h) {
fclose(h);
}
_SOKOL_PRIVATE bool _sfetch_file_handle_valid(_sfetch_file_handle_t h) {
return h != _SFETCH_INVALID_FILE_HANDLE;
}
_SOKOL_PRIVATE uint32_t _sfetch_file_size(_sfetch_file_handle_t h) {
fseek(h, 0, SEEK_END);
return (uint32_t) ftell(h);
}
_SOKOL_PRIVATE bool _sfetch_file_read(_sfetch_file_handle_t h, uint32_t offset, uint32_t num_bytes, void* ptr) {
fseek(h, (long)offset, SEEK_SET);
return num_bytes == fread(ptr, 1, num_bytes, h);
}
_SOKOL_PRIVATE bool _sfetch_thread_init(_sfetch_thread_t* thread, _sfetch_thread_func_t thread_func, void* thread_arg) {
SOKOL_ASSERT(thread && !thread->valid && !thread->stop_requested);
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutex_init(&thread->incoming_mutex, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutexattr_init(&attr);
pthread_mutex_init(&thread->outgoing_mutex, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutexattr_init(&attr);
pthread_mutex_init(&thread->running_mutex, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutexattr_init(&attr);
pthread_mutex_init(&thread->stop_mutex, &attr);
pthread_mutexattr_destroy(&attr);
pthread_condattr_t cond_attr;
pthread_condattr_init(&cond_attr);
pthread_cond_init(&thread->incoming_cond, &cond_attr);
pthread_condattr_destroy(&cond_attr);
pthread_mutex_lock(&thread->running_mutex);
int res = pthread_create(&thread->thread, 0, thread_func, thread_arg);
thread->valid = (0 == res);
pthread_mutex_unlock(&thread->running_mutex);
return thread->valid;
}
_SOKOL_PRIVATE void _sfetch_thread_request_stop(_sfetch_thread_t* thread) {
pthread_mutex_lock(&thread->stop_mutex);
thread->stop_requested = true;
pthread_mutex_unlock(&thread->stop_mutex);
}
_SOKOL_PRIVATE bool _sfetch_thread_stop_requested(_sfetch_thread_t* thread) {
pthread_mutex_lock(&thread->stop_mutex);
bool stop_requested = thread->stop_requested;
pthread_mutex_unlock(&thread->stop_mutex);
return stop_requested;
}
_SOKOL_PRIVATE void _sfetch_thread_join(_sfetch_thread_t* thread) {
SOKOL_ASSERT(thread);
if (thread->valid) {
pthread_mutex_lock(&thread->incoming_mutex);
_sfetch_thread_request_stop(thread);
pthread_cond_signal(&thread->incoming_cond);
pthread_mutex_unlock(&thread->incoming_mutex);
pthread_join(thread->thread, 0);
thread->valid = false;
}
pthread_mutex_destroy(&thread->stop_mutex);
pthread_mutex_destroy(&thread->running_mutex);
pthread_mutex_destroy(&thread->incoming_mutex);
pthread_mutex_destroy(&thread->outgoing_mutex);
pthread_cond_destroy(&thread->incoming_cond);
}
_SOKOL_PRIVATE void _sfetch_thread_entered(_sfetch_thread_t* thread) {
pthread_mutex_lock(&thread->running_mutex);
}
_SOKOL_PRIVATE void _sfetch_thread_leaving(_sfetch_thread_t* thread) {
pthread_mutex_unlock(&thread->running_mutex);
}
_SOKOL_PRIVATE void _sfetch_thread_enqueue_incoming(_sfetch_thread_t* thread, _sfetch_ring_t* incoming, _sfetch_ring_t* src) {
SOKOL_ASSERT(thread && thread->valid);
SOKOL_ASSERT(incoming && incoming->buf);
SOKOL_ASSERT(src && src->buf);
if (!_sfetch_ring_empty(src)) {
pthread_mutex_lock(&thread->incoming_mutex);
while (!_sfetch_ring_full(incoming) && !_sfetch_ring_empty(src)) {
_sfetch_ring_enqueue(incoming, _sfetch_ring_dequeue(src));
}
pthread_cond_signal(&thread->incoming_cond);
pthread_mutex_unlock(&thread->incoming_mutex);
}
}
_SOKOL_PRIVATE uint32_t _sfetch_thread_dequeue_incoming(_sfetch_thread_t* thread, _sfetch_ring_t* incoming) {
SOKOL_ASSERT(thread && thread->valid);
SOKOL_ASSERT(incoming && incoming->buf);
pthread_mutex_lock(&thread->incoming_mutex);
while (_sfetch_ring_empty(incoming) && !thread->stop_requested) {
pthread_cond_wait(&thread->incoming_cond, &thread->incoming_mutex);
}
uint32_t item = 0;
if (!thread->stop_requested) {
item = _sfetch_ring_dequeue(incoming);
}
pthread_mutex_unlock(&thread->incoming_mutex);
return item;
}
_SOKOL_PRIVATE bool _sfetch_thread_enqueue_outgoing(_sfetch_thread_t* thread, _sfetch_ring_t* outgoing, uint32_t item) {
SOKOL_ASSERT(thread && thread->valid);
SOKOL_ASSERT(outgoing && outgoing->buf);
SOKOL_ASSERT(0 != item);
pthread_mutex_lock(&thread->outgoing_mutex);
bool result = false;
if (!_sfetch_ring_full(outgoing)) {
_sfetch_ring_enqueue(outgoing, item);
}
pthread_mutex_unlock(&thread->outgoing_mutex);
return result;
}
_SOKOL_PRIVATE void _sfetch_thread_dequeue_outgoing(_sfetch_thread_t* thread, _sfetch_ring_t* outgoing, _sfetch_ring_t* dst) {
SOKOL_ASSERT(thread && thread->valid);
SOKOL_ASSERT(outgoing && outgoing->buf);
SOKOL_ASSERT(dst && dst->buf);
pthread_mutex_lock(&thread->outgoing_mutex);
while (!_sfetch_ring_full(dst) && !_sfetch_ring_empty(outgoing)) {
_sfetch_ring_enqueue(dst, _sfetch_ring_dequeue(outgoing));
}
pthread_mutex_unlock(&thread->outgoing_mutex);
}
#endif
#if _SFETCH_PLATFORM_WINDOWS
_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));
memset(dst, 0, (size_t)dst_num_bytes);
const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t);
const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0);
if ((dst_needed > 0) && (dst_needed < dst_chars)) {
MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars);
return true;
}
else {
return false;
}
}
_SOKOL_PRIVATE _sfetch_file_handle_t _sfetch_file_open(const _sfetch_path_t* path) {
wchar_t w_path[SFETCH_MAX_PATH];
if (!_sfetch_win32_utf8_to_wide(path->buf, w_path, sizeof(w_path))) {
SOKOL_LOG("_sfetch_file_open: error converting UTF-8 path to wide string");
return 0;
}
_sfetch_file_handle_t h = CreateFileW(
w_path,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
return h;
}
_SOKOL_PRIVATE void _sfetch_file_close(_sfetch_file_handle_t h) {
CloseHandle(h);
}
_SOKOL_PRIVATE bool _sfetch_file_handle_valid(_sfetch_file_handle_t h) {
return h != _SFETCH_INVALID_FILE_HANDLE;
}
_SOKOL_PRIVATE uint32_t _sfetch_file_size(_sfetch_file_handle_t h) {
return GetFileSize(h, NULL);
}
_SOKOL_PRIVATE bool _sfetch_file_read(_sfetch_file_handle_t h, uint32_t offset, uint32_t num_bytes, void* ptr) {
LARGE_INTEGER offset_li;
offset_li.QuadPart = offset;
BOOL seek_res = SetFilePointerEx(h, offset_li, NULL, FILE_BEGIN);
if (seek_res) {
DWORD bytes_read = 0;
BOOL read_res = ReadFile(h, ptr, (DWORD)num_bytes, &bytes_read, NULL);
return read_res && (bytes_read == num_bytes);
}
else {
return false;
}
}
_SOKOL_PRIVATE bool _sfetch_thread_init(_sfetch_thread_t* thread, _sfetch_thread_func_t thread_func, void* thread_arg) {
SOKOL_ASSERT(thread && !thread->valid && !thread->stop_requested);
thread->incoming_event = CreateEventA(NULL, FALSE, FALSE, NULL);
SOKOL_ASSERT(NULL != thread->incoming_event);
InitializeCriticalSection(&thread->incoming_critsec);
InitializeCriticalSection(&thread->outgoing_critsec);
InitializeCriticalSection(&thread->running_critsec);
InitializeCriticalSection(&thread->stop_critsec);
EnterCriticalSection(&thread->running_critsec);
const SIZE_T stack_size = 512 * 1024;
thread->thread = CreateThread(NULL, stack_size, thread_func, thread_arg, 0, NULL);
thread->valid = (NULL != thread->thread);
LeaveCriticalSection(&thread->running_critsec);
return thread->valid;
}
_SOKOL_PRIVATE void _sfetch_thread_request_stop(_sfetch_thread_t* thread) {
EnterCriticalSection(&thread->stop_critsec);
thread->stop_requested = true;
LeaveCriticalSection(&thread->stop_critsec);
}
_SOKOL_PRIVATE bool _sfetch_thread_stop_requested(_sfetch_thread_t* thread) {
EnterCriticalSection(&thread->stop_critsec);
bool stop_requested = thread->stop_requested;
LeaveCriticalSection(&thread->stop_critsec);
return stop_requested;
}
_SOKOL_PRIVATE void _sfetch_thread_join(_sfetch_thread_t* thread) {
if (thread->valid) {
EnterCriticalSection(&thread->incoming_critsec);
_sfetch_thread_request_stop(thread);
BOOL set_event_res = SetEvent(thread->incoming_event);
_SOKOL_UNUSED(set_event_res);
SOKOL_ASSERT(set_event_res);
LeaveCriticalSection(&thread->incoming_critsec);
WaitForSingleObject(thread->thread, INFINITE);
CloseHandle(thread->thread);
thread->valid = false;
}
CloseHandle(thread->incoming_event);
DeleteCriticalSection(&thread->stop_critsec);
DeleteCriticalSection(&thread->running_critsec);
DeleteCriticalSection(&thread->outgoing_critsec);
DeleteCriticalSection(&thread->incoming_critsec);
}
_SOKOL_PRIVATE void _sfetch_thread_entered(_sfetch_thread_t* thread) {
EnterCriticalSection(&thread->running_critsec);
}
_SOKOL_PRIVATE void _sfetch_thread_leaving(_sfetch_thread_t* thread) {
LeaveCriticalSection(&thread->running_critsec);
}
_SOKOL_PRIVATE void _sfetch_thread_enqueue_incoming(_sfetch_thread_t* thread, _sfetch_ring_t* incoming, _sfetch_ring_t* src) {
SOKOL_ASSERT(thread && thread->valid);
SOKOL_ASSERT(incoming && incoming->buf);
SOKOL_ASSERT(src && src->buf);
if (!_sfetch_ring_empty(src)) {
EnterCriticalSection(&thread->incoming_critsec);
while (!_sfetch_ring_full(incoming) && !_sfetch_ring_empty(src)) {
_sfetch_ring_enqueue(incoming, _sfetch_ring_dequeue(src));
}
LeaveCriticalSection(&thread->incoming_critsec);
BOOL set_event_res = SetEvent(thread->incoming_event);
_SOKOL_UNUSED(set_event_res);
SOKOL_ASSERT(set_event_res);
}
}
_SOKOL_PRIVATE uint32_t _sfetch_thread_dequeue_incoming(_sfetch_thread_t* thread, _sfetch_ring_t* incoming) {
SOKOL_ASSERT(thread && thread->valid);
SOKOL_ASSERT(incoming && incoming->buf);
EnterCriticalSection(&thread->incoming_critsec);
while (_sfetch_ring_empty(incoming) && !thread->stop_requested) {
LeaveCriticalSection(&thread->incoming_critsec);
WaitForSingleObject(thread->incoming_event, INFINITE);
EnterCriticalSection(&thread->incoming_critsec);
}
uint32_t item = 0;
if (!thread->stop_requested) {
item = _sfetch_ring_dequeue(incoming);
}
LeaveCriticalSection(&thread->incoming_critsec);
return item;
}
_SOKOL_PRIVATE bool _sfetch_thread_enqueue_outgoing(_sfetch_thread_t* thread, _sfetch_ring_t* outgoing, uint32_t item) {
SOKOL_ASSERT(thread && thread->valid);
SOKOL_ASSERT(outgoing && outgoing->buf);
EnterCriticalSection(&thread->outgoing_critsec);
bool result = false;
if (!_sfetch_ring_full(outgoing)) {
_sfetch_ring_enqueue(outgoing, item);
}
LeaveCriticalSection(&thread->outgoing_critsec);
return result;
}
_SOKOL_PRIVATE void _sfetch_thread_dequeue_outgoing(_sfetch_thread_t* thread, _sfetch_ring_t* outgoing, _sfetch_ring_t* dst) {
SOKOL_ASSERT(thread && thread->valid);
SOKOL_ASSERT(outgoing && outgoing->buf);
SOKOL_ASSERT(dst && dst->buf);
EnterCriticalSection(&thread->outgoing_critsec);
while (!_sfetch_ring_full(dst) && !_sfetch_ring_empty(outgoing)) {
_sfetch_ring_enqueue(dst, _sfetch_ring_dequeue(outgoing));
}
LeaveCriticalSection(&thread->outgoing_critsec);
}
#endif
#if _SFETCH_HAS_THREADS
_SOKOL_PRIVATE void _sfetch_request_handler(_sfetch_t* ctx, uint32_t slot_id) {
_sfetch_state_t state;
_sfetch_path_t* path;
_sfetch_item_thread_t* thread;
_sfetch_buffer_t* buffer;
uint32_t chunk_size;
{
_sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id);
if (!item) {
return;
}
state = item->state;
SOKOL_ASSERT((state == _SFETCH_STATE_FETCHING) ||
(state == _SFETCH_STATE_PAUSED) ||
(state == _SFETCH_STATE_FAILED));
path = &item->path;
thread = &item->thread;
buffer = &item->buffer;
chunk_size = item->chunk_size;
}
if (thread->failed) {
return;
}
if (state == _SFETCH_STATE_FETCHING) {
if ((buffer->ptr == 0) || (buffer->size == 0)) {
thread->error_code = SFETCH_ERROR_NO_BUFFER;
thread->failed = true;
}
else {
if (!_sfetch_file_handle_valid(thread->file_handle)) {
SOKOL_ASSERT(path->buf[0]);
SOKOL_ASSERT(thread->fetched_offset == 0);
SOKOL_ASSERT(thread->fetched_size == 0);
thread->file_handle = _sfetch_file_open(path);
if (_sfetch_file_handle_valid(thread->file_handle)) {
thread->content_size = _sfetch_file_size(thread->file_handle);
}
else {
thread->error_code = SFETCH_ERROR_FILE_NOT_FOUND;
thread->failed = true;
}
}
if (!thread->failed) {
uint32_t read_offset = 0;
uint32_t bytes_to_read = 0;
if (chunk_size == 0) {
if (thread->content_size <= buffer->size) {
bytes_to_read = thread->content_size;
read_offset = 0;
}
else {
thread->error_code = SFETCH_ERROR_BUFFER_TOO_SMALL;
thread->failed = true;
}
}
else {
if (chunk_size <= buffer->size) {
bytes_to_read = chunk_size;
read_offset = thread->fetched_offset;
if ((read_offset + bytes_to_read) > thread->content_size) {
bytes_to_read = thread->content_size - read_offset;
}
}
else {
thread->error_code = SFETCH_ERROR_BUFFER_TOO_SMALL;
thread->failed = true;
}
}
if (!thread->failed) {
if (_sfetch_file_read(thread->file_handle, read_offset, bytes_to_read, buffer->ptr)) {
thread->fetched_size = bytes_to_read;
thread->fetched_offset += bytes_to_read;
}
else {
thread->error_code = SFETCH_ERROR_UNEXPECTED_EOF;
thread->failed = true;
}
}
}
}
SOKOL_ASSERT(thread->fetched_offset <= thread->content_size);
if (thread->failed || (thread->fetched_offset == thread->content_size)) {
if (_sfetch_file_handle_valid(thread->file_handle)) {
_sfetch_file_close(thread->file_handle);
thread->file_handle = _SFETCH_INVALID_FILE_HANDLE;
}
thread->finished = true;
}
}
}
#if _SFETCH_PLATFORM_WINDOWS
_SOKOL_PRIVATE DWORD WINAPI _sfetch_channel_thread_func(LPVOID arg) {
#else
_SOKOL_PRIVATE void* _sfetch_channel_thread_func(void* arg) {
#endif
_sfetch_channel_t* chn = (_sfetch_channel_t*) arg;
_sfetch_thread_entered(&chn->thread);
while (!_sfetch_thread_stop_requested(&chn->thread)) {
uint32_t slot_id = _sfetch_thread_dequeue_incoming(&chn->thread, &chn->thread_incoming);
if (!_sfetch_thread_stop_requested(&chn->thread)) {
SOKOL_ASSERT(0 != slot_id);
chn->request_handler(chn->ctx, slot_id);
SOKOL_ASSERT(!_sfetch_ring_full(&chn->thread_outgoing));
_sfetch_thread_enqueue_outgoing(&chn->thread, &chn->thread_outgoing, slot_id);
}
}
_sfetch_thread_leaving(&chn->thread);
return 0;
}
#endif
#if _SFETCH_PLATFORM_EMSCRIPTEN
EM_JS(void, sfetch_js_send_head_request, (uint32_t slot_id, const char* path_cstr), {
var path_str = UTF8ToString(path_cstr);
var req = new XMLHttpRequest();
req.open('HEAD', path_str);
req.onreadystatechange = function() {
if (this.readyState == this.DONE) {
if (this.status == 200) {
var content_length = this.getResponseHeader('Content-Length');
__sfetch_emsc_head_response(slot_id, content_length);
}
else {
__sfetch_emsc_failed_http_status(slot_id, this.status);
}
}
};
req.send();
});
EM_JS(void, sfetch_js_send_get_request, (uint32_t slot_id, const char* path_cstr, uint32_t offset, uint32_t bytes_to_read, void* buf_ptr, uint32_t buf_size), {
var path_str = UTF8ToString(path_cstr);
var req = new XMLHttpRequest();
req.open('GET', path_str);
req.responseType = 'arraybuffer';
var need_range_request = (bytes_to_read > 0);
if (need_range_request) {
req.setRequestHeader('Range', 'bytes='+offset+'-'+(offset+bytes_to_read-1));
}
req.onreadystatechange = function() {
if (this.readyState == this.DONE) {
if ((this.status == 206) || ((this.status == 200) && !need_range_request)) {
var u8_array = new Uint8Array(req.response);
var content_fetched_size = u8_array.length;
if (content_fetched_size <= buf_size) {
HEAPU8.set(u8_array, buf_ptr);
__sfetch_emsc_get_response(slot_id, bytes_to_read, content_fetched_size);
}
else {
__sfetch_emsc_failed_buffer_too_small(slot_id);
}
}
else {
__sfetch_emsc_failed_http_status(slot_id, this.status);
}
}
};
req.send();
});
#ifdef __cplusplus
extern "C" {
#endif
void _sfetch_emsc_send_get_request(uint32_t slot_id, _sfetch_item_t* item) {
if ((item->buffer.ptr == 0) || (item->buffer.size == 0)) {
item->thread.error_code = SFETCH_ERROR_NO_BUFFER;
item->thread.failed = true;
}
else {
uint32_t offset = 0;
uint32_t bytes_to_read = 0;
if (item->chunk_size > 0) {
SOKOL_ASSERT(item->thread.content_size > 0);
SOKOL_ASSERT(item->thread.http_range_offset < item->thread.content_size);
bytes_to_read = item->thread.content_size - item->thread.http_range_offset;
if (bytes_to_read > item->chunk_size) {
bytes_to_read = item->chunk_size;
}
SOKOL_ASSERT(bytes_to_read > 0);
offset = item->thread.http_range_offset;
}
sfetch_js_send_get_request(slot_id, item->path.buf, offset, bytes_to_read, item->buffer.ptr, item->buffer.size);
}
}
EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_head_response(uint32_t slot_id, uint32_t content_length) {
_sfetch_t* ctx = _sfetch_ctx();
if (ctx && ctx->valid) {
_sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id);
if (item) {
SOKOL_ASSERT(item->buffer.ptr && (item->buffer.size > 0));
item->thread.content_size = content_length;
_sfetch_emsc_send_get_request(slot_id, item);
}
}
}
EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_get_response(uint32_t slot_id, uint32_t range_fetched_size, uint32_t content_fetched_size) {
_sfetch_t* ctx = _sfetch_ctx();
if (ctx && ctx->valid) {
_sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id);
if (item) {
item->thread.fetched_size = content_fetched_size;
item->thread.fetched_offset += content_fetched_size;
item->thread.http_range_offset += range_fetched_size;
if (item->chunk_size == 0) {
item->thread.finished = true;
}
else if (item->thread.http_range_offset >= item->thread.content_size) {
item->thread.finished = true;
}
_sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id);
}
}
}
EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_failed_http_status(uint32_t slot_id, uint32_t http_status) {
_sfetch_t* ctx = _sfetch_ctx();
if (ctx && ctx->valid) {
_sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id);
if (item) {
if (http_status == 404) {
item->thread.error_code = SFETCH_ERROR_FILE_NOT_FOUND;
}
else {
item->thread.error_code = SFETCH_ERROR_INVALID_HTTP_STATUS;
}
item->thread.failed = true;
item->thread.finished = true;
_sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id);
}
}
}
EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_failed_buffer_too_small(uint32_t slot_id) {
_sfetch_t* ctx = _sfetch_ctx();
if (ctx && ctx->valid) {
_sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id);
if (item) {
item->thread.error_code = SFETCH_ERROR_BUFFER_TOO_SMALL;
item->thread.failed = true;
item->thread.finished = true;
_sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id);
}
}
}
#ifdef __cplusplus
}
#endif
_SOKOL_PRIVATE void _sfetch_request_handler(_sfetch_t* ctx, uint32_t slot_id) {
_sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id);
if (!item) {
return;
}
if (item->state == _SFETCH_STATE_FETCHING) {
if ((item->chunk_size > 0) && (item->thread.content_size == 0)) {
sfetch_js_send_head_request(slot_id, item->path.buf);
}
else {
_sfetch_emsc_send_get_request(slot_id, item);
}
}
else {
_sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id);
}
if (item->thread.failed) {
item->thread.finished = true;
}
}
#endif
_SOKOL_PRIVATE void _sfetch_channel_discard(_sfetch_channel_t* chn) {
SOKOL_ASSERT(chn);
#if _SFETCH_HAS_THREADS
if (chn->valid) {
_sfetch_thread_join(&chn->thread);
}
_sfetch_ring_discard(&chn->thread_incoming);
_sfetch_ring_discard(&chn->thread_outgoing);
#endif
_sfetch_ring_discard(&chn->free_lanes);
_sfetch_ring_discard(&chn->user_sent);
_sfetch_ring_discard(&chn->user_incoming);
_sfetch_ring_discard(&chn->user_outgoing);
_sfetch_ring_discard(&chn->free_lanes);
chn->valid = false;
}
_SOKOL_PRIVATE bool _sfetch_channel_init(_sfetch_channel_t* chn, _sfetch_t* ctx, uint32_t num_items, uint32_t num_lanes, void (*request_handler)(_sfetch_t* ctx, uint32_t)) {
SOKOL_ASSERT(chn && (num_items > 0) && request_handler);
SOKOL_ASSERT(!chn->valid);
bool valid = true;
chn->request_handler = request_handler;
chn->ctx = ctx;
valid &= _sfetch_ring_init(&chn->free_lanes, num_lanes);
for (uint32_t lane = 0; lane < num_lanes; lane++) {
_sfetch_ring_enqueue(&chn->free_lanes, lane);
}
valid &= _sfetch_ring_init(&chn->user_sent, num_items);
valid &= _sfetch_ring_init(&chn->user_incoming, num_lanes);
valid &= _sfetch_ring_init(&chn->user_outgoing, num_lanes);
#if _SFETCH_HAS_THREADS
valid &= _sfetch_ring_init(&chn->thread_incoming, num_lanes);
valid &= _sfetch_ring_init(&chn->thread_outgoing, num_lanes);
#endif
if (valid) {
chn->valid = true;
#if _SFETCH_HAS_THREADS
_sfetch_thread_init(&chn->thread, _sfetch_channel_thread_func, chn);
#endif
return true;
}
else {
_sfetch_channel_discard(chn);
return false;
}
}
_SOKOL_PRIVATE bool _sfetch_channel_send(_sfetch_channel_t* chn, uint32_t slot_id) {
SOKOL_ASSERT(chn && chn->valid);
if (!_sfetch_ring_full(&chn->user_sent)) {
_sfetch_ring_enqueue(&chn->user_sent, slot_id);
return true;
}
else {
SOKOL_LOG("sfetch_send: user_sent queue is full)");
return false;
}
}
_SOKOL_PRIVATE void _sfetch_invoke_response_callback(_sfetch_item_t* item) {
sfetch_response_t response;
memset(&response, 0, sizeof(response));
response.handle = item->handle;
response.dispatched = (item->state == _SFETCH_STATE_DISPATCHED);
response.fetched = (item->state == _SFETCH_STATE_FETCHED);
response.paused = (item->state == _SFETCH_STATE_PAUSED);
response.finished = item->user.finished;
response.failed = (item->state == _SFETCH_STATE_FAILED);
response.cancelled = item->user.cancel;
response.error_code = item->user.error_code;
response.channel = item->channel;
response.lane = item->lane;
response.path = item->path.buf;
response.user_data = item->user.user_data;
response.fetched_offset = item->user.fetched_offset - item->user.fetched_size;
response.fetched_size = item->user.fetched_size;
response.buffer_ptr = item->buffer.ptr;
response.buffer_size = item->buffer.size;
item->callback(&response);
}
_SOKOL_PRIVATE void _sfetch_channel_dowork(_sfetch_channel_t* chn, _sfetch_pool_t* pool) {
const uint32_t num_sent = _sfetch_ring_count(&chn->user_sent);
const uint32_t avail_lanes = _sfetch_ring_count(&chn->free_lanes);
const uint32_t num_move = (num_sent < avail_lanes) ? num_sent : avail_lanes;
for (uint32_t i = 0; i < num_move; i++) {
const uint32_t slot_id = _sfetch_ring_dequeue(&chn->user_sent);
_sfetch_item_t* item = _sfetch_pool_item_lookup(pool, slot_id);
SOKOL_ASSERT(item);
SOKOL_ASSERT(item->state == _SFETCH_STATE_ALLOCATED);
item->state = _SFETCH_STATE_DISPATCHED;
item->lane = _sfetch_ring_dequeue(&chn->free_lanes);
if (0 == item->buffer.ptr) {
_sfetch_invoke_response_callback(item);
}
_sfetch_ring_enqueue(&chn->user_incoming, slot_id);
}
const uint32_t num_incoming = _sfetch_ring_count(&chn->user_incoming);
for (uint32_t i = 0; i < num_incoming; i++) {
const uint32_t slot_id = _sfetch_ring_peek(&chn->user_incoming, i);
_sfetch_item_t* item = _sfetch_pool_item_lookup(pool, slot_id);
SOKOL_ASSERT(item);
SOKOL_ASSERT(item->state != _SFETCH_STATE_INITIAL);
SOKOL_ASSERT(item->state != _SFETCH_STATE_FETCHING);
if (item->user.pause) {
item->state = _SFETCH_STATE_PAUSED;
item->user.pause = false;
}
if (item->user.cont) {
if (item->state == _SFETCH_STATE_PAUSED) {
item->state = _SFETCH_STATE_FETCHED;
}
item->user.cont = false;
}
if (item->user.cancel) {
item->state = _SFETCH_STATE_FAILED;
item->user.finished = true;
}
switch (item->state) {
case _SFETCH_STATE_DISPATCHED:
case _SFETCH_STATE_FETCHED:
item->state = _SFETCH_STATE_FETCHING;
break;
default: break;
}
}
#if _SFETCH_HAS_THREADS
_sfetch_thread_enqueue_incoming(&chn->thread, &chn->thread_incoming, &chn->user_incoming);
_sfetch_thread_dequeue_outgoing(&chn->thread, &chn->thread_outgoing, &chn->user_outgoing);
#else
while (!_sfetch_ring_empty(&chn->user_incoming)) {
uint32_t slot_id = _sfetch_ring_dequeue(&chn->user_incoming);
_sfetch_request_handler(chn->ctx, slot_id);
}
#endif
while (!_sfetch_ring_empty(&chn->user_outgoing)) {
const uint32_t slot_id = _sfetch_ring_dequeue(&chn->user_outgoing);
SOKOL_ASSERT(slot_id);
_sfetch_item_t* item = _sfetch_pool_item_lookup(pool, slot_id);
SOKOL_ASSERT(item && item->callback);
SOKOL_ASSERT(item->state != _SFETCH_STATE_INITIAL);
SOKOL_ASSERT(item->state != _SFETCH_STATE_ALLOCATED);
SOKOL_ASSERT(item->state != _SFETCH_STATE_DISPATCHED);
SOKOL_ASSERT(item->state != _SFETCH_STATE_FETCHED);
item->user.fetched_offset = item->thread.fetched_offset;
item->user.fetched_size = item->thread.fetched_size;
if (item->user.cancel) {
item->user.error_code = SFETCH_ERROR_CANCELLED;
}
else {
item->user.error_code = item->thread.error_code;
}
if (item->thread.finished) {
item->user.finished = true;
}
if (item->thread.failed) {
item->state = _SFETCH_STATE_FAILED;
}
else if (item->state == _SFETCH_STATE_FETCHING) {
item->state = _SFETCH_STATE_FETCHED;
}
_sfetch_invoke_response_callback(item);
if (item->user.finished) {
_sfetch_ring_enqueue(&chn->free_lanes, item->lane);
_sfetch_pool_item_free(pool, slot_id);
}
else {
_sfetch_ring_enqueue(&chn->user_incoming, slot_id);
}
}
}
_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) {
SOKOL_LOG("_sfetch_validate_request: request.channel too big!");
return false;
}
if (!req->path) {
SOKOL_LOG("_sfetch_validate_request: request.path is null!");
return false;
}
if (strlen(req->path) >= (SFETCH_MAX_PATH-1)) {
SOKOL_LOG("_sfetch_validate_request: request.path is too long (must be < SFETCH_MAX_PATH-1)");
return false;
}
if (!req->callback) {
SOKOL_LOG("_sfetch_validate_request: request.callback missing");
return false;
}
if (req->chunk_size > req->buffer_size) {
SOKOL_LOG("_sfetch_validate_request: request.chunk_size is greater request.buffer_size)");
return false;
}
if (req->user_data_ptr && (req->user_data_size == 0)) {
SOKOL_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)) {
SOKOL_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)) {
SOKOL_LOG("_sfetch_validate_request: request.user_data_size is too big (see SFETCH_MAX_USERDATA_UINT64");
return false;
}
#else
(void)(ctx && req);
#endif
return true;
}
SOKOL_API_IMPL void sfetch_setup(const sfetch_desc_t* desc) {
SOKOL_ASSERT(desc);
SOKOL_ASSERT((desc->_start_canary == 0) && (desc->_end_canary == 0));
SOKOL_ASSERT(0 == _sfetch);
_sfetch = (_sfetch_t*) SOKOL_MALLOC(sizeof(_sfetch_t));
SOKOL_ASSERT(_sfetch);
memset(_sfetch, 0, sizeof(_sfetch_t));
_sfetch_t* ctx = _sfetch_ctx();
ctx->desc = *desc;
ctx->setup = true;
ctx->valid = true;
ctx->desc.max_requests = _sfetch_def(ctx->desc.max_requests, 128);
ctx->desc.num_channels = _sfetch_def(ctx->desc.num_channels, 1);
ctx->desc.num_lanes = _sfetch_def(ctx->desc.num_lanes, 1);
if (ctx->desc.num_channels > SFETCH_MAX_CHANNELS) {
ctx->desc.num_channels = SFETCH_MAX_CHANNELS;
SOKOL_LOG("sfetch_setup: clamping num_channels to SFETCH_MAX_CHANNELS");
}
ctx->valid &= _sfetch_pool_init(&ctx->pool, ctx->desc.max_requests);
for (uint32_t i = 0; i < ctx->desc.num_channels; i++) {
ctx->valid &= _sfetch_channel_init(&ctx->chn[i], ctx, ctx->desc.max_requests, ctx->desc.num_lanes, _sfetch_request_handler);
}
}
SOKOL_API_IMPL void sfetch_shutdown(void) {
_sfetch_t* ctx = _sfetch_ctx();
SOKOL_ASSERT(ctx && ctx->setup);
ctx->valid = false;
for (uint32_t i = 0; i < ctx->desc.num_channels; i++) {
if (ctx->chn[i].valid) {
_sfetch_channel_discard(&ctx->chn[i]);
}
}
_sfetch_pool_discard(&ctx->pool);
ctx->setup = false;
SOKOL_FREE(ctx);
_sfetch = 0;
}
SOKOL_API_IMPL bool sfetch_valid(void) {
_sfetch_t* ctx = _sfetch_ctx();
return ctx && ctx->valid;
}
SOKOL_API_IMPL sfetch_desc_t sfetch_desc(void) {
_sfetch_t* ctx = _sfetch_ctx();
SOKOL_ASSERT(ctx && ctx->valid);
return ctx->desc;
}
SOKOL_API_IMPL int sfetch_max_userdata_bytes(void) {
return SFETCH_MAX_USERDATA_UINT64 * 8;
}
SOKOL_API_IMPL int sfetch_max_path(void) {
return SFETCH_MAX_PATH;
}
SOKOL_API_IMPL bool sfetch_handle_valid(sfetch_handle_t h) {
_sfetch_t* ctx = _sfetch_ctx();
SOKOL_ASSERT(ctx && ctx->valid);
if (h.id == 0) {
return false;
}
return 0 != _sfetch_pool_item_lookup(&ctx->pool, h.id);
}
SOKOL_API_IMPL sfetch_handle_t sfetch_send(const sfetch_request_t* request) {
_sfetch_t* ctx = _sfetch_ctx();
SOKOL_ASSERT(ctx && ctx->setup);
SOKOL_ASSERT(request && (request->_start_canary == 0) && (request->_end_canary == 0));
const sfetch_handle_t invalid_handle = _sfetch_make_handle(0);
if (!ctx->valid) {
return invalid_handle;
}
if (!_sfetch_validate_request(ctx, request)) {
return invalid_handle;
}
SOKOL_ASSERT(request->channel < ctx->desc.num_channels);
uint32_t slot_id = _sfetch_pool_item_alloc(&ctx->pool, request);
if (0 == slot_id) {
SOKOL_LOG("sfetch_send: request pool exhausted (too many active requests)");
return invalid_handle;
}
if (!_sfetch_channel_send(&ctx->chn[request->channel], slot_id)) {
_sfetch_pool_item_free(&ctx->pool, slot_id);
return invalid_handle;
}
return _sfetch_make_handle(slot_id);
}
SOKOL_API_IMPL void sfetch_dowork(void) {
_sfetch_t* ctx = _sfetch_ctx();
SOKOL_ASSERT(ctx && ctx->setup);
if (!ctx->valid) {
return;
}
ctx->in_callback = true;
for (int pass = 0; pass < 2; pass++) {
for (uint32_t chn_index = 0; chn_index < ctx->desc.num_channels; chn_index++) {
_sfetch_channel_dowork(&ctx->chn[chn_index], &ctx->pool);
}
}
ctx->in_callback = false;
}
SOKOL_API_IMPL void sfetch_bind_buffer(sfetch_handle_t h, void* buffer_ptr, uint32_t buffer_size) {
_sfetch_t* ctx = _sfetch_ctx();
SOKOL_ASSERT(ctx && ctx->valid);
SOKOL_ASSERT(ctx->in_callback);
_sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id);
if (item) {
SOKOL_ASSERT((0 == item->buffer.ptr) && (0 == item->buffer.size));
item->buffer.ptr = (uint8_t*) buffer_ptr;
item->buffer.size = buffer_size;
}
}
SOKOL_API_IMPL void* sfetch_unbind_buffer(sfetch_handle_t h) {
_sfetch_t* ctx = _sfetch_ctx();
SOKOL_ASSERT(ctx && ctx->valid);
SOKOL_ASSERT(ctx->in_callback);
_sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id);
if (item) {
void* prev_buf_ptr = item->buffer.ptr;
item->buffer.ptr = 0;
item->buffer.size = 0;
return prev_buf_ptr;
}
else {
return 0;
}
}
SOKOL_API_IMPL void sfetch_pause(sfetch_handle_t h) {
_sfetch_t* ctx = _sfetch_ctx();
SOKOL_ASSERT(ctx && ctx->valid);
_sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id);
if (item) {
item->user.pause = true;
item->user.cont = false;
}
}
SOKOL_API_IMPL void sfetch_continue(sfetch_handle_t h) {
_sfetch_t* ctx = _sfetch_ctx();
SOKOL_ASSERT(ctx && ctx->valid);
_sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id);
if (item) {
item->user.cont = true;
item->user.pause = false;
}
}
SOKOL_API_IMPL void sfetch_cancel(sfetch_handle_t h) {
_sfetch_t* ctx = _sfetch_ctx();
SOKOL_ASSERT(ctx && ctx->valid);
_sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id);
if (item) {
item->user.cont = false;
item->user.pause = false;
item->user.cancel = true;
}
}
#endif