//------------------------------------------------------------------------------ // sokol-fetch-test.c // // FIXME: simulate allocation errors //------------------------------------------------------------------------------ #define SOKOL_IMPL #define SFETCH_MAX_USERDATA_UINT64 (8) #define SFETCH_MAX_PATH (32) #include "sokol_fetch.h" #include "utest.h" #define T(b) EXPECT_TRUE(b) #define TSTR(s0, s1) EXPECT_TRUE(0 == strcmp(s0,s1)) static uint8_t load_file_buf[500000]; static const uint64_t combatsignal_file_size = 409482; typedef struct { int a, b, c; } userdata_t; static const _sfetch_item_t zeroed_item = {0}; #ifdef _WIN32 #include static void sleep_ms(int ms) { Sleep((DWORD)ms); } #else #include static void sleep_ms(uint32_t ms) { usleep(ms * 1000); } #endif /* private implementation functions */ UTEST(sokol_fetch, path_make) { const char* str31 = "1234567890123456789012345678901"; const char* str32 = "12345678901234567890123456789012"; // max allowed string length (MAX_PATH - 1) _sfetch_path_t p31 = _sfetch_path_make(str31); TSTR(p31.buf, str31); // overflow _sfetch_path_t p32 = _sfetch_path_make(str32); T(p32.buf[0] == 0); } UTEST(sokol_fetch, make_id) { uint32_t slot_id = _sfetch_make_id(123, 456); T(slot_id == ((456<<16)|123)); T(_sfetch_slot_index(slot_id) == 123); } UTEST(sokol_fetch, item_init_discard) { userdata_t user_data = { .a = 123, .b = 456, .c = 789 }; sfetch_request_t request = { .channel = 4, .path = "hello_world.txt", .chunk_size = 128, .user_data = SFETCH_RANGE(user_data) }; _sfetch_item_t item = zeroed_item; uint32_t slot_id = _sfetch_make_id(1, 1); _sfetch_item_init(&item, slot_id, &request); T(item.handle.id == slot_id); T(item.channel == 4); T(item.lane == _SFETCH_INVALID_LANE); T(item.chunk_size == 128); T(item.state == _SFETCH_STATE_INITIAL); TSTR(item.path.buf, request.path); T(item.user.user_data_size == sizeof(userdata_t)); const userdata_t* ud = (const userdata_t*) item.user.user_data; T((((uintptr_t)ud) & 0x7) == 0); // check alignment T(ud->a == 123); T(ud->b == 456); T(ud->c == 789); item.state = _SFETCH_STATE_FETCHING; _sfetch_item_discard(&item); T(item.handle.id == 0); T(item.path.buf[0] == 0); T(item.state == _SFETCH_STATE_INITIAL); T(item.user.user_data_size == 0); T(item.user.user_data[0] == 0); } UTEST(sokol_fetch, item_init_path_overflow) { sfetch_request_t request = { .path = "012345678901234567890123456789012", }; _sfetch_item_t item = zeroed_item; _sfetch_item_init(&item, _sfetch_make_id(1, 1), &request); T(item.path.buf[0] == 0); } UTEST(sokol_fetch, item_init_userdata_overflow) { uint8_t big_data[128] = { 0xFF }; sfetch_request_t request = { .path = "hello_world.txt", .user_data = SFETCH_RANGE(big_data), }; _sfetch_item_t item = zeroed_item; _sfetch_item_init(&item, _sfetch_make_id(1, 1), &request); T(item.user.user_data_size == 0); T(item.user.user_data[0] == 0); } UTEST(sokol_fetch, pool_init_discard) { sfetch_setup(&(sfetch_desc_t){0}); _sfetch_pool_t pool = {0}; const uint32_t num_items = 127; T(_sfetch_pool_init(&pool, num_items)); T(pool.valid); T(pool.size == 128); T(pool.free_top == 127); T(pool.free_slots[0] == 127); T(pool.free_slots[1] == 126); T(pool.free_slots[126] == 1); _sfetch_pool_discard(&pool); T(!pool.valid); T(pool.free_slots == 0); T(pool.items == 0); sfetch_shutdown(); } UTEST(sokol_fetch, pool_alloc_free) { sfetch_setup(&(sfetch_desc_t){0}); uint8_t buf[32]; _sfetch_pool_t pool = {0}; const uint32_t num_items = 16; _sfetch_pool_init(&pool, num_items); uint32_t slot_id = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path = "hello_world.txt", .buffer = SFETCH_RANGE(buf), }); T(slot_id == 0x00010001); T(pool.items[1].state == _SFETCH_STATE_ALLOCATED); T(pool.items[1].handle.id == slot_id); TSTR(pool.items[1].path.buf, "hello_world.txt"); T(pool.items[1].buffer.ptr == buf); T(pool.items[1].buffer.size == sizeof(buf)); T(pool.free_top == 15); _sfetch_pool_item_free(&pool, slot_id); T(pool.items[1].handle.id == 0); T(pool.items[1].state == _SFETCH_STATE_INITIAL); T(pool.items[1].path.buf[0] == 0); T(pool.items[1].buffer.ptr == 0); T(pool.items[1].buffer.size == 0); T(pool.free_top == 16); _sfetch_pool_discard(&pool); sfetch_shutdown(); } UTEST(sokol_fetch, pool_overflow) { sfetch_setup(&(sfetch_desc_t){0}); _sfetch_pool_t pool = {0}; _sfetch_pool_init(&pool, 4); uint32_t id0 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path0" }); uint32_t id1 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path1" }); uint32_t id2 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path2" }); uint32_t id3 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path3" }); // next alloc should fail uint32_t id4 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path4" }); T(id0 == 0x00010001); T(id1 == 0x00010002); T(id2 == 0x00010003); T(id3 == 0x00010004); T(id4 == 0); T(pool.items[1].handle.id == id0); T(pool.items[2].handle.id == id1); T(pool.items[3].handle.id == id2); T(pool.items[4].handle.id == id3); // free one item, alloc should work now _sfetch_pool_item_free(&pool, id0); uint32_t id5 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path5" }); T(id5 == 0x00020001); T(pool.items[1].handle.id == id5); TSTR(pool.items[1].path.buf, "path5"); _sfetch_pool_discard(&pool); sfetch_shutdown(); } UTEST(sokol_fetch, lookup_item) { sfetch_setup(&(sfetch_desc_t){0}); _sfetch_pool_t pool = {0}; _sfetch_pool_init(&pool, 4); uint32_t id0 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path0" }); uint32_t id1 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path1" }); const _sfetch_item_t* item0 = _sfetch_pool_item_lookup(&pool, id0); const _sfetch_item_t* item1 = _sfetch_pool_item_lookup(&pool, id1); T(item0 == &pool.items[1]); T(item1 == &pool.items[2]); /* invalid handle always returns 0-ptr */ T(0 == _sfetch_pool_item_lookup(&pool, _sfetch_make_id(0, 0))); /* free an item and make sure it's detected as dangling */ _sfetch_pool_item_free(&pool, id0); T(0 == _sfetch_pool_item_lookup(&pool, id0)); _sfetch_pool_discard(&pool); sfetch_shutdown(); } UTEST(sokol_fetch, ring_init_discard) { sfetch_setup(&(sfetch_desc_t){0}); _sfetch_ring_t ring = {0}; const uint32_t num_slots = 4; T(_sfetch_ring_init(&ring, num_slots)); T(ring.head == 0); T(ring.tail == 0); T(ring.num == (num_slots + 1)); T(ring.buf); _sfetch_ring_discard(&ring); T(ring.head == 0); T(ring.tail == 0); T(ring.num == 0); T(ring.buf == 0); sfetch_shutdown(); } UTEST(sokol_fetch, ring_enqueue_dequeue) { sfetch_setup(&(sfetch_desc_t){0}); _sfetch_ring_t ring = {0}; const uint32_t num_slots = 4; _sfetch_ring_init(&ring, num_slots); T(_sfetch_ring_count(&ring) == 0); T(_sfetch_ring_empty(&ring)); T(!_sfetch_ring_full(&ring)); for (uint32_t i = 0; i < num_slots; i++) { T(!_sfetch_ring_full(&ring)); _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1)); T(_sfetch_ring_count(&ring) == (i+1)); T(!_sfetch_ring_empty(&ring)); } T(_sfetch_ring_count(&ring) == 4); T(!_sfetch_ring_empty(&ring)); T(_sfetch_ring_full(&ring)); for (uint32_t i = 0; i < num_slots; i++) { T(_sfetch_ring_peek(&ring, i) == _sfetch_make_id(1, i+1)); } for (uint32_t i = 0; i < num_slots; i++) { T(!_sfetch_ring_empty(&ring)); const uint32_t slot_id = _sfetch_ring_dequeue(&ring); T(slot_id == _sfetch_make_id(1, i+1)); T(!_sfetch_ring_full(&ring)); } T(_sfetch_ring_count(&ring) == 0); T(_sfetch_ring_empty(&ring)); T(!_sfetch_ring_full(&ring)); _sfetch_ring_discard(&ring); sfetch_shutdown(); } UTEST(sokol_fetch, ring_wrap_around) { sfetch_setup(&(sfetch_desc_t){0}); _sfetch_ring_t ring = {0}; _sfetch_ring_init(&ring, 4); uint32_t i = 0; for (i = 0; i < 4; i++) { _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1)); } T(_sfetch_ring_full(&ring)); for (; i < 64; i++) { T(_sfetch_ring_full(&ring)); T(_sfetch_ring_dequeue(&ring) == _sfetch_make_id(1, (i - 3))); T(!_sfetch_ring_full(&ring)); _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1)); } T(_sfetch_ring_full(&ring)); for (i = 0; i < 4; i++) { T(_sfetch_ring_dequeue(&ring) == _sfetch_make_id(1, (i + 61))); } T(_sfetch_ring_empty(&ring)); _sfetch_ring_discard(&ring); sfetch_shutdown(); } UTEST(sokol_fetch, ring_wrap_count) { sfetch_setup(&(sfetch_desc_t){0}); _sfetch_ring_t ring = {0}; _sfetch_ring_init(&ring, 8); // add and remove 4 items to move tail to the middle for (uint32_t i = 0; i < 4; i++) { _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1)); _sfetch_ring_dequeue(&ring); T(_sfetch_ring_empty(&ring)); } // add another 8 items for (uint32_t i = 0; i < 8; i++) { _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1)); } // now test, dequeue and test... T(_sfetch_ring_full(&ring)); for (uint32_t i = 0; i < 8; i++) { T(_sfetch_ring_count(&ring) == (8 - i)); _sfetch_ring_dequeue(&ring); } T(_sfetch_ring_count(&ring) == 0); T(_sfetch_ring_empty(&ring)); _sfetch_ring_discard(&ring); sfetch_shutdown(); } /* NOTE: channel_worker is called from a thread */ static int num_processed_items = 0; static void channel_worker(_sfetch_t* ctx, uint32_t slot_id) { (void)ctx; (void)slot_id; num_processed_items++; } UTEST(sokol_fetch, channel_init_discard) { sfetch_setup(&(sfetch_desc_t){0}); num_processed_items = 0; _sfetch_channel_t chn = {0}; const uint32_t num_slots = 12; const uint32_t num_lanes = 64; _sfetch_channel_init(&chn, 0, num_slots, num_lanes, channel_worker); T(chn.valid); T(_sfetch_ring_full(&chn.free_lanes)); T(_sfetch_ring_empty(&chn.user_sent)); T(_sfetch_ring_empty(&chn.user_incoming)); #if !defined(__EMSCRIPTEN__) T(_sfetch_ring_empty(&chn.thread_incoming)); T(_sfetch_ring_empty(&chn.thread_outgoing)); #endif T(_sfetch_ring_empty(&chn.user_outgoing)); _sfetch_channel_discard(&chn); T(!chn.valid); sfetch_shutdown(); } /* public API functions */ UTEST(sokol_fetch, setup_shutdown) { sfetch_setup(&(sfetch_desc_t){0}); T(sfetch_valid()); // check default values T(sfetch_desc().max_requests == 128); T(sfetch_desc().num_channels == 1); T(sfetch_desc().num_lanes == 1); sfetch_shutdown(); T(!sfetch_valid()); } UTEST(sokol_fetch, setup_too_many_channels) { /* try to initialize with too many channels, this should clamp to SFETCH_MAX_CHANNELS */ sfetch_setup(&(sfetch_desc_t){ .num_channels = 64 }); T(sfetch_valid()); T(sfetch_desc().num_channels == SFETCH_MAX_CHANNELS); sfetch_shutdown(); } UTEST(sokol_fetch, max_path) { T(sfetch_max_path() == SFETCH_MAX_PATH); } UTEST(sokol_fetch, max_userdata) { T(sfetch_max_userdata_bytes() == (SFETCH_MAX_USERDATA_UINT64 * sizeof(uint64_t))); } static uint8_t fail_open_buffer[128]; static bool fail_open_passed; static void fail_open_callback(const sfetch_response_t* response) { /* if opening a file fails, it will immediate switch into CLOSED state */ if ((response->failed) && (response->error_code == SFETCH_ERROR_FILE_NOT_FOUND)) { fail_open_passed = true; } } UTEST(sokol_fetch, fail_open) { sfetch_setup(&(sfetch_desc_t){0}); sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ .path = "non_existing_file.txt", .callback = fail_open_callback, .buffer = SFETCH_RANGE(fail_open_buffer), }); fail_open_passed = false; int frame_count = 0; const int max_frames = 10000; while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { sfetch_dowork(); sleep_ms(1); } T(frame_count < max_frames); T(fail_open_passed); sfetch_shutdown(); } static bool load_file_fixed_buffer_passed; // The file callback is called from the "current user thread" (the same // thread where the sfetch_send() for this request was called). Note that you // can call sfetch_setup/shutdown() on multiple threads, each thread will // get its own thread-local "sokol-fetch instance" and its own set of // IO-channel threads. static void load_file_fixed_buffer_callback(const sfetch_response_t* response) { // when loading the whole file at once, the fetched state // is the best place to grab/process the data if (response->fetched) { if ((response->data_offset == 0) && (response->data.ptr == load_file_buf) && (response->data.size == combatsignal_file_size) && (response->buffer.ptr == load_file_buf) && (response->buffer.size == sizeof(load_file_buf)) && response->finished) { load_file_fixed_buffer_passed = true; } } } UTEST(sokol_fetch, load_file_fixed_buffer) { memset(load_file_buf, 0, sizeof(load_file_buf)); sfetch_setup(&(sfetch_desc_t){0}); // send a load-request for a file where we know the max size upfront, // so we can provide a buffer right in the fetch request (otherwise // the buffer needs to be provided in the callback when the request // is in OPENED state, since only then the file size will be known). sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ .path = "comsi.s3m", .callback = load_file_fixed_buffer_callback, .buffer = SFETCH_RANGE(load_file_buf), }); // simulate a frame-loop for as long as the request is in flight, normally // the sfetch_dowork() function is just called somewhere in the frame // to pump messages in and out of the IO threads, and invoke user-callbacks int frame_count = 0; const int max_frames = 10000; while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { sfetch_dowork(); sleep_ms(1); } T(frame_count < max_frames); T(load_file_fixed_buffer_passed); sfetch_shutdown(); } /* tests whether files with unknown size are processed correctly */ static bool load_file_unknown_size_opened_passed; static bool load_file_unknown_size_fetched_passed; static void load_file_unknown_size_callback(const sfetch_response_t* response) { if (response->dispatched) { if ((response->data_offset == 0) && (response->data.ptr == 0) && (response->data.size == 0) && (response->buffer.ptr == 0) && (response->buffer.size == 0) && !response->finished) { load_file_unknown_size_opened_passed = true; sfetch_bind_buffer(response->handle, SFETCH_RANGE(load_file_buf)); } } else if (response->fetched) { if ((response->data_offset == 0) && (response->data.ptr == load_file_buf) && (response->data.size == combatsignal_file_size) && (response->buffer.ptr == load_file_buf) && (response->buffer.size == sizeof(load_file_buf)) && response->finished) { load_file_unknown_size_fetched_passed = true; } } } UTEST(sokol_fetch, load_file_unknown_size) { memset(load_file_buf, 0, sizeof(load_file_buf)); sfetch_setup(&(sfetch_desc_t){0}); sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ .path = "comsi.s3m", .callback = load_file_unknown_size_callback }); int frame_count = 0; const int max_frames = 10000; while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { sfetch_dowork(); sleep_ms(1); } T(frame_count < max_frames); T(load_file_unknown_size_opened_passed); T(load_file_unknown_size_fetched_passed); sfetch_shutdown(); } /* tests whether not providing a buffer in OPENED properly fails */ static bool load_file_no_buffer_opened_passed; static bool load_file_no_buffer_failed_passed; static void load_file_no_buffer_callback(const sfetch_response_t* response) { if (response->dispatched) { if ((response->data_offset == 0) && (response->data.ptr == 0) && (response->data.size == 0) && (response->buffer.ptr == 0) && (response->buffer.size == 0) && !response->finished) { /* DO NOT provide a buffer here, see if that properly fails */ load_file_no_buffer_opened_passed = true; } } else if ((response->failed) && (response->error_code == SFETCH_ERROR_NO_BUFFER)) { if (load_file_no_buffer_opened_passed) { load_file_no_buffer_failed_passed = true; } } } UTEST(sokol_fetch, load_file_no_buffer) { memset(load_file_buf, 0, sizeof(load_file_buf)); sfetch_setup(&(sfetch_desc_t){0}); sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ .path = "comsi.s3m", .callback = load_file_no_buffer_callback }); int frame_count = 0; const int max_frames = 10000; while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { sfetch_dowork(); sleep_ms(1); } T(frame_count < max_frames); T(load_file_no_buffer_opened_passed); T(load_file_no_buffer_failed_passed); sfetch_shutdown(); } static bool load_file_too_small_passed; static uint8_t load_file_too_small_buf[8192]; static void load_file_too_small_callback(const sfetch_response_t* response) { if (response->failed && (response->error_code == SFETCH_ERROR_BUFFER_TOO_SMALL)) { load_file_too_small_passed = true; } } UTEST(sokol_fetch, load_file_too_small_buffer) { memset(load_file_buf, 0, sizeof(load_file_buf)); sfetch_setup(&(sfetch_desc_t){0}); sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ .path = "comsi.s3m", .callback = load_file_too_small_callback, .buffer = SFETCH_RANGE(load_file_too_small_buf), }); int frame_count = 0; const int max_frames = 10000; while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { sfetch_dowork(); sleep_ms(1); } T(frame_count < max_frames); T(load_file_too_small_passed); sfetch_shutdown(); } /* test loading a big file via a small chunk-buffer, the callback will be called multiple times with the FETCHED state until the entire file is loaded */ static bool load_file_chunked_passed; static uint8_t load_chunk_buf[8192]; static uint8_t load_file_chunked_content[500000]; static void load_file_chunked_callback(const sfetch_response_t* response) { if (response->fetched) { uint8_t* dst = &load_file_chunked_content[response->data_offset]; const uint8_t* src = response->data.ptr; size_t num_bytes = response->data.size; memcpy(dst, src, num_bytes); if (response->finished) { load_file_chunked_passed = true; } } } UTEST(sokol_fetch, load_file_chunked) { memset(load_file_buf, 0, sizeof(load_file_buf)); memset(load_chunk_buf, 0, sizeof(load_chunk_buf)); memset(load_file_chunked_content, 0, sizeof(load_file_chunked_content)); load_file_fixed_buffer_passed = false; sfetch_setup(&(sfetch_desc_t){0}); // request for chunked-loading sfetch_handle_t h0 = sfetch_send(&(sfetch_request_t){ .path = "comsi.s3m", .callback = load_file_chunked_callback, .buffer = SFETCH_RANGE(load_chunk_buf), .chunk_size = sizeof(load_chunk_buf) }); // request for all-in-one loading for comparing with the chunked buffer sfetch_handle_t h1 = sfetch_send(&(sfetch_request_t){ .path = "comsi.s3m", .callback = load_file_fixed_buffer_callback, .buffer = SFETCH_RANGE(load_file_buf), }); int frame_count = 0; const int max_frames = 10000; while ((sfetch_handle_valid(h0) || sfetch_handle_valid(h1)) && (frame_count++ < max_frames)) { sfetch_dowork(); sleep_ms(1); } T(frame_count < max_frames); T(load_file_chunked_passed); T(0 == memcmp(load_file_chunked_content, load_file_buf, combatsignal_file_size)); sfetch_shutdown(); } /* load N big files in small chunks interleaved on the same channel via lanes */ #define LOAD_FILE_LANES_NUM_LANES (4) static uint8_t load_file_lanes_chunk_buf[LOAD_FILE_LANES_NUM_LANES][8192]; static uint8_t load_file_lanes_content[LOAD_FILE_LANES_NUM_LANES][500000]; static int load_file_lanes_passed[LOAD_FILE_LANES_NUM_LANES]; static void load_file_lanes_callback(const sfetch_response_t* response) { assert((response->channel == 0) && (response->lane < LOAD_FILE_LANES_NUM_LANES)); if (response->fetched) { uint8_t* dst = &load_file_lanes_content[response->lane][response->data_offset]; const uint8_t* src = response->data.ptr; size_t num_bytes = response->data.size; memcpy(dst, src, num_bytes); if (response->finished) { load_file_lanes_passed[response->lane]++; } } } UTEST(sokol_fetch, load_file_lanes) { for (int i = 0; i < LOAD_FILE_LANES_NUM_LANES; i++) { memset(load_file_lanes_content[i], i, sizeof(load_file_lanes_content[i])); } sfetch_setup(&(sfetch_desc_t){ .num_channels = 1, .num_lanes = LOAD_FILE_LANES_NUM_LANES, }); sfetch_handle_t h[LOAD_FILE_LANES_NUM_LANES]; for (int lane = 0; lane < LOAD_FILE_LANES_NUM_LANES; lane++) { h[lane] = sfetch_send(&(sfetch_request_t){ .path = "comsi.s3m", .callback = load_file_lanes_callback, .buffer = { .ptr = load_file_lanes_chunk_buf[lane], .size = sizeof(load_file_lanes_chunk_buf[0]) }, .chunk_size = sizeof(load_file_lanes_chunk_buf[0]) }); } bool done = false; int frame_count = 0; const int max_frames = 10000; while (!done && (frame_count++ < max_frames)) { done = true; for (int i = 0; i < LOAD_FILE_LANES_NUM_LANES; i++) { done &= !sfetch_handle_valid(h[i]); } sfetch_dowork(); sleep_ms(1); } T(frame_count < max_frames); for (int i = 0; i < LOAD_FILE_LANES_NUM_LANES; i++) { T(1 == load_file_lanes_passed[i]); T(0 == memcmp(load_file_lanes_content[0], load_file_lanes_content[i], combatsignal_file_size)); } sfetch_shutdown(); } /* same as above, but issue more requests than available lanes to test if rate-limiting works */ #define LOAD_FILE_THROTTLE_NUM_LANES (4) #define LOAD_FILE_THROTTLE_NUM_PASSES (3) #define LOAD_FILE_THROTTLE_NUM_REQUESTS (12) // lanes * passes static uint8_t load_file_throttle_chunk_buf[LOAD_FILE_THROTTLE_NUM_LANES][128000]; static uint8_t load_file_throttle_content[LOAD_FILE_THROTTLE_NUM_PASSES][LOAD_FILE_THROTTLE_NUM_LANES][500000]; static int load_file_throttle_passed[LOAD_FILE_THROTTLE_NUM_LANES]; static void load_file_throttle_callback(const sfetch_response_t* response) { assert((response->channel == 0) && (response->lane < LOAD_FILE_LANES_NUM_LANES)); if (response->fetched) { assert(load_file_throttle_passed[response->lane] < LOAD_FILE_THROTTLE_NUM_PASSES); uint8_t* dst = &load_file_throttle_content[load_file_throttle_passed[response->lane]][response->lane][response->data_offset]; const uint8_t* src = response->data.ptr; size_t num_bytes = response->data.size; memcpy(dst, src, num_bytes); if (response->finished) { load_file_throttle_passed[response->lane]++; } } } UTEST(sokol_fetch, load_file_throttle) { for (int pass = 0; pass < LOAD_FILE_THROTTLE_NUM_PASSES; pass++) { for (int lane = 0; lane < LOAD_FILE_THROTTLE_NUM_LANES; lane++) { memset(load_file_throttle_content[pass][lane], 10*pass+lane, sizeof(load_file_throttle_content[pass][lane])); } } sfetch_setup(&(sfetch_desc_t){ .num_channels = 1, .num_lanes = LOAD_FILE_THROTTLE_NUM_LANES, }); sfetch_handle_t h[LOAD_FILE_THROTTLE_NUM_REQUESTS]; for (int i = 0; i < LOAD_FILE_THROTTLE_NUM_REQUESTS; i++) { h[i] = sfetch_send(&(sfetch_request_t){ .path = "comsi.s3m", .callback = load_file_throttle_callback, .buffer = { .ptr = load_file_throttle_chunk_buf[i % LOAD_FILE_THROTTLE_NUM_LANES], .size = sizeof(load_file_throttle_chunk_buf[0]), }, .chunk_size = sizeof(load_file_throttle_chunk_buf[0]) }); T(sfetch_handle_valid(h[i])); } bool done = false; int frame_count = 0; const int max_frames = 10000; while (!done && (frame_count++ < max_frames)) { done = true; for (int i = 0; i < LOAD_FILE_THROTTLE_NUM_REQUESTS; i++) { done &= !sfetch_handle_valid(h[i]); } sfetch_dowork(); sleep_ms(1); } T(frame_count < max_frames); for (int lane = 0; lane < LOAD_FILE_THROTTLE_NUM_LANES; lane++) { T(LOAD_FILE_THROTTLE_NUM_PASSES == load_file_throttle_passed[lane]); for (int pass = 0; pass < LOAD_FILE_THROTTLE_NUM_PASSES; pass++) { T(0 == memcmp(load_file_throttle_content[0][0], load_file_throttle_content[pass][lane], combatsignal_file_size)); } } sfetch_shutdown(); } /* test parallel fetches on multiple channels */ #define LOAD_CHANNEL_NUM_CHANNELS (16) static uint8_t load_channel_buf[LOAD_CHANNEL_NUM_CHANNELS][500000]; static bool load_channel_passed[LOAD_CHANNEL_NUM_CHANNELS]; void load_channel_callback(const sfetch_response_t* response) { assert(response->channel < LOAD_CHANNEL_NUM_CHANNELS); assert(!load_channel_passed[response->channel]); if (response->fetched) { if ((response->data.size == combatsignal_file_size) && response->finished) { load_channel_passed[response->channel] = true; } } } UTEST(sokol_fetch, load_channel) { for (int chn = 0; chn < LOAD_CHANNEL_NUM_CHANNELS; chn++) { memset(load_channel_buf[chn], chn, sizeof(load_channel_buf[chn])); } sfetch_setup(&(sfetch_desc_t){ .num_channels = LOAD_CHANNEL_NUM_CHANNELS }); sfetch_handle_t h[LOAD_CHANNEL_NUM_CHANNELS]; for (uint32_t chn = 0; chn < LOAD_CHANNEL_NUM_CHANNELS; chn++) { h[chn] = sfetch_send(&(sfetch_request_t){ .path = "comsi.s3m", .channel = chn, .callback = load_channel_callback, .buffer = SFETCH_RANGE(load_channel_buf[chn]), }); } bool done = false; int frame_count = 0; const int max_frames = 100000; while (!done && (frame_count++ < max_frames)) { done = true; for (int i = 0; i < LOAD_CHANNEL_NUM_CHANNELS; i++) { done &= !sfetch_handle_valid(h[i]); } sfetch_dowork(); sleep_ms(1); } T(frame_count < max_frames); for (int chn = 0; chn < LOAD_CHANNEL_NUM_CHANNELS; chn++) { T(load_channel_passed[chn]); T(0 == memcmp(load_channel_buf[0], load_channel_buf[chn], combatsignal_file_size)); } sfetch_shutdown(); } static bool load_file_cancel_passed = false; static void load_file_cancel_callback(const sfetch_response_t* response) { if (response->dispatched) { sfetch_cancel(response->handle); } if (response->cancelled && response->finished && response->failed && (response->error_code == SFETCH_ERROR_CANCELLED)) { load_file_cancel_passed = true; } } UTEST(sokol_fetch, load_file_cancel) { sfetch_setup(&(sfetch_desc_t){ .num_channels = 1 }); sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ .path = "comsi.s3m", .callback = load_file_cancel_callback, }); int frame_count = 0; const int max_frames = 10000; while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { sfetch_dowork(); sleep_ms(1); } T(frame_count < max_frames); T(load_file_cancel_passed); sfetch_shutdown(); } static bool load_file_cancel_before_dispatch_passed = false; static void load_file_cancel_before_dispatch_callback(const sfetch_response_t* response) { // cancelled, finished, failed and error code must all be set if (response->cancelled && response->finished && response->failed && (response->error_code == SFETCH_ERROR_CANCELLED)) { load_file_cancel_before_dispatch_passed = true; } } UTEST(sokol_fetch, load_file_cancel_before_dispatch) { sfetch_setup(&(sfetch_desc_t){ .num_channels = 1, }); sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ .path = "comsi.s3m", .callback = load_file_cancel_before_dispatch_callback, }); sfetch_cancel(h); sfetch_dowork(); T(load_file_cancel_before_dispatch_passed); sfetch_shutdown(); } static bool load_file_cancel_after_dispatch_passed = false; static void load_file_cancel_after_dispatch_callback(const sfetch_response_t* response) { // when cancelled, then finished, failed and error code must all be set if (response->cancelled && response->finished && response->failed && (response->error_code == SFETCH_ERROR_CANCELLED)) { load_file_cancel_after_dispatch_passed = true; } } UTEST(sokol_fetch, load_file_cancel_after_dispatch) { sfetch_setup(&(sfetch_desc_t){ .num_channels = 1, }); sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ .path = "comsi.s3m", .callback = load_file_cancel_after_dispatch_callback, .buffer = SFETCH_RANGE(load_file_buf), }); int frame_count = 0; const int max_frames = 10000; while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { sfetch_dowork(); sfetch_cancel(h); sleep_ms(1); } T(frame_count < max_frames); T(load_file_cancel_after_dispatch_passed); sfetch_shutdown(); }