sokol_fetch_test.c 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862
  1. //------------------------------------------------------------------------------
  2. // sokol-fetch-test.c
  3. //
  4. // FIXME: simulate allocation errors
  5. //------------------------------------------------------------------------------
  6. #define SOKOL_IMPL
  7. #define SFETCH_MAX_USERDATA_UINT64 (8)
  8. #define SFETCH_MAX_PATH (32)
  9. #include "sokol_fetch.h"
  10. #include "utest.h"
  11. #define T(b) EXPECT_TRUE(b)
  12. #define TSTR(s0, s1) EXPECT_TRUE(0 == strcmp(s0,s1))
  13. static uint8_t load_file_buf[500000];
  14. static const uint64_t combatsignal_file_size = 409482;
  15. typedef struct {
  16. int a, b, c;
  17. } userdata_t;
  18. static const _sfetch_item_t zeroed_item = {0};
  19. #ifdef _WIN32
  20. #include <windows.h>
  21. static void sleep_ms(int ms) {
  22. Sleep((DWORD)ms);
  23. }
  24. #else
  25. #include <unistd.h>
  26. static void sleep_ms(uint32_t ms) {
  27. usleep(ms * 1000);
  28. }
  29. #endif
  30. /* private implementation functions */
  31. UTEST(sokol_fetch, path_make) {
  32. const char* str31 = "1234567890123456789012345678901";
  33. const char* str32 = "12345678901234567890123456789012";
  34. // max allowed string length (MAX_PATH - 1)
  35. _sfetch_path_t p31 = _sfetch_path_make(str31);
  36. TSTR(p31.buf, str31);
  37. // overflow
  38. _sfetch_path_t p32 = _sfetch_path_make(str32);
  39. T(p32.buf[0] == 0);
  40. }
  41. UTEST(sokol_fetch, make_id) {
  42. uint32_t slot_id = _sfetch_make_id(123, 456);
  43. T(slot_id == ((456<<16)|123));
  44. T(_sfetch_slot_index(slot_id) == 123);
  45. }
  46. UTEST(sokol_fetch, item_init_discard) {
  47. userdata_t user_data = {
  48. .a = 123,
  49. .b = 456,
  50. .c = 789
  51. };
  52. sfetch_request_t request = {
  53. .channel = 4,
  54. .path = "hello_world.txt",
  55. .chunk_size = 128,
  56. .user_data = SFETCH_RANGE(user_data)
  57. };
  58. _sfetch_item_t item = zeroed_item;
  59. uint32_t slot_id = _sfetch_make_id(1, 1);
  60. _sfetch_item_init(&item, slot_id, &request);
  61. T(item.handle.id == slot_id);
  62. T(item.channel == 4);
  63. T(item.lane == _SFETCH_INVALID_LANE);
  64. T(item.chunk_size == 128);
  65. T(item.state == _SFETCH_STATE_INITIAL);
  66. TSTR(item.path.buf, request.path);
  67. T(item.user.user_data_size == sizeof(userdata_t));
  68. const userdata_t* ud = (const userdata_t*) item.user.user_data;
  69. T((((uintptr_t)ud) & 0x7) == 0); // check alignment
  70. T(ud->a == 123);
  71. T(ud->b == 456);
  72. T(ud->c == 789);
  73. item.state = _SFETCH_STATE_FETCHING;
  74. _sfetch_item_discard(&item);
  75. T(item.handle.id == 0);
  76. T(item.path.buf[0] == 0);
  77. T(item.state == _SFETCH_STATE_INITIAL);
  78. T(item.user.user_data_size == 0);
  79. T(item.user.user_data[0] == 0);
  80. }
  81. UTEST(sokol_fetch, item_init_path_overflow) {
  82. sfetch_request_t request = {
  83. .path = "012345678901234567890123456789012",
  84. };
  85. _sfetch_item_t item = zeroed_item;
  86. _sfetch_item_init(&item, _sfetch_make_id(1, 1), &request);
  87. T(item.path.buf[0] == 0);
  88. }
  89. UTEST(sokol_fetch, item_init_userdata_overflow) {
  90. uint8_t big_data[128] = { 0xFF };
  91. sfetch_request_t request = {
  92. .path = "hello_world.txt",
  93. .user_data = SFETCH_RANGE(big_data),
  94. };
  95. _sfetch_item_t item = zeroed_item;
  96. _sfetch_item_init(&item, _sfetch_make_id(1, 1), &request);
  97. T(item.user.user_data_size == 0);
  98. T(item.user.user_data[0] == 0);
  99. }
  100. UTEST(sokol_fetch, pool_init_discard) {
  101. sfetch_setup(&(sfetch_desc_t){0});
  102. _sfetch_pool_t pool = {0};
  103. const uint32_t num_items = 127;
  104. T(_sfetch_pool_init(&pool, num_items));
  105. T(pool.valid);
  106. T(pool.size == 128);
  107. T(pool.free_top == 127);
  108. T(pool.free_slots[0] == 127);
  109. T(pool.free_slots[1] == 126);
  110. T(pool.free_slots[126] == 1);
  111. _sfetch_pool_discard(&pool);
  112. T(!pool.valid);
  113. T(pool.free_slots == 0);
  114. T(pool.items == 0);
  115. sfetch_shutdown();
  116. }
  117. UTEST(sokol_fetch, pool_alloc_free) {
  118. sfetch_setup(&(sfetch_desc_t){0});
  119. uint8_t buf[32];
  120. _sfetch_pool_t pool = {0};
  121. const uint32_t num_items = 16;
  122. _sfetch_pool_init(&pool, num_items);
  123. uint32_t slot_id = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){
  124. .path = "hello_world.txt",
  125. .buffer = SFETCH_RANGE(buf),
  126. });
  127. T(slot_id == 0x00010001);
  128. T(pool.items[1].state == _SFETCH_STATE_ALLOCATED);
  129. T(pool.items[1].handle.id == slot_id);
  130. TSTR(pool.items[1].path.buf, "hello_world.txt");
  131. T(pool.items[1].buffer.ptr == buf);
  132. T(pool.items[1].buffer.size == sizeof(buf));
  133. T(pool.free_top == 15);
  134. _sfetch_pool_item_free(&pool, slot_id);
  135. T(pool.items[1].handle.id == 0);
  136. T(pool.items[1].state == _SFETCH_STATE_INITIAL);
  137. T(pool.items[1].path.buf[0] == 0);
  138. T(pool.items[1].buffer.ptr == 0);
  139. T(pool.items[1].buffer.size == 0);
  140. T(pool.free_top == 16);
  141. _sfetch_pool_discard(&pool);
  142. sfetch_shutdown();
  143. }
  144. UTEST(sokol_fetch, pool_overflow) {
  145. sfetch_setup(&(sfetch_desc_t){0});
  146. _sfetch_pool_t pool = {0};
  147. _sfetch_pool_init(&pool, 4);
  148. uint32_t id0 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path0" });
  149. uint32_t id1 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path1" });
  150. uint32_t id2 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path2" });
  151. uint32_t id3 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path3" });
  152. // next alloc should fail
  153. uint32_t id4 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path4" });
  154. T(id0 == 0x00010001);
  155. T(id1 == 0x00010002);
  156. T(id2 == 0x00010003);
  157. T(id3 == 0x00010004);
  158. T(id4 == 0);
  159. T(pool.items[1].handle.id == id0);
  160. T(pool.items[2].handle.id == id1);
  161. T(pool.items[3].handle.id == id2);
  162. T(pool.items[4].handle.id == id3);
  163. // free one item, alloc should work now
  164. _sfetch_pool_item_free(&pool, id0);
  165. uint32_t id5 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path5" });
  166. T(id5 == 0x00020001);
  167. T(pool.items[1].handle.id == id5);
  168. TSTR(pool.items[1].path.buf, "path5");
  169. _sfetch_pool_discard(&pool);
  170. sfetch_shutdown();
  171. }
  172. UTEST(sokol_fetch, lookup_item) {
  173. sfetch_setup(&(sfetch_desc_t){0});
  174. _sfetch_pool_t pool = {0};
  175. _sfetch_pool_init(&pool, 4);
  176. uint32_t id0 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path0" });
  177. uint32_t id1 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path1" });
  178. const _sfetch_item_t* item0 = _sfetch_pool_item_lookup(&pool, id0);
  179. const _sfetch_item_t* item1 = _sfetch_pool_item_lookup(&pool, id1);
  180. T(item0 == &pool.items[1]);
  181. T(item1 == &pool.items[2]);
  182. /* invalid handle always returns 0-ptr */
  183. T(0 == _sfetch_pool_item_lookup(&pool, _sfetch_make_id(0, 0)));
  184. /* free an item and make sure it's detected as dangling */
  185. _sfetch_pool_item_free(&pool, id0);
  186. T(0 == _sfetch_pool_item_lookup(&pool, id0));
  187. _sfetch_pool_discard(&pool);
  188. sfetch_shutdown();
  189. }
  190. UTEST(sokol_fetch, ring_init_discard) {
  191. sfetch_setup(&(sfetch_desc_t){0});
  192. _sfetch_ring_t ring = {0};
  193. const uint32_t num_slots = 4;
  194. T(_sfetch_ring_init(&ring, num_slots));
  195. T(ring.head == 0);
  196. T(ring.tail == 0);
  197. T(ring.num == (num_slots + 1));
  198. T(ring.buf);
  199. _sfetch_ring_discard(&ring);
  200. T(ring.head == 0);
  201. T(ring.tail == 0);
  202. T(ring.num == 0);
  203. T(ring.buf == 0);
  204. sfetch_shutdown();
  205. }
  206. UTEST(sokol_fetch, ring_enqueue_dequeue) {
  207. sfetch_setup(&(sfetch_desc_t){0});
  208. _sfetch_ring_t ring = {0};
  209. const uint32_t num_slots = 4;
  210. _sfetch_ring_init(&ring, num_slots);
  211. T(_sfetch_ring_count(&ring) == 0);
  212. T(_sfetch_ring_empty(&ring));
  213. T(!_sfetch_ring_full(&ring));
  214. for (uint32_t i = 0; i < num_slots; i++) {
  215. T(!_sfetch_ring_full(&ring));
  216. _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1));
  217. T(_sfetch_ring_count(&ring) == (i+1));
  218. T(!_sfetch_ring_empty(&ring));
  219. }
  220. T(_sfetch_ring_count(&ring) == 4);
  221. T(!_sfetch_ring_empty(&ring));
  222. T(_sfetch_ring_full(&ring));
  223. for (uint32_t i = 0; i < num_slots; i++) {
  224. T(_sfetch_ring_peek(&ring, i) == _sfetch_make_id(1, i+1));
  225. }
  226. for (uint32_t i = 0; i < num_slots; i++) {
  227. T(!_sfetch_ring_empty(&ring));
  228. const uint32_t slot_id = _sfetch_ring_dequeue(&ring);
  229. T(slot_id == _sfetch_make_id(1, i+1));
  230. T(!_sfetch_ring_full(&ring));
  231. }
  232. T(_sfetch_ring_count(&ring) == 0);
  233. T(_sfetch_ring_empty(&ring));
  234. T(!_sfetch_ring_full(&ring));
  235. _sfetch_ring_discard(&ring);
  236. sfetch_shutdown();
  237. }
  238. UTEST(sokol_fetch, ring_wrap_around) {
  239. sfetch_setup(&(sfetch_desc_t){0});
  240. _sfetch_ring_t ring = {0};
  241. _sfetch_ring_init(&ring, 4);
  242. uint32_t i = 0;
  243. for (i = 0; i < 4; i++) {
  244. _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1));
  245. }
  246. T(_sfetch_ring_full(&ring));
  247. for (; i < 64; i++) {
  248. T(_sfetch_ring_full(&ring));
  249. T(_sfetch_ring_dequeue(&ring) == _sfetch_make_id(1, (i - 3)));
  250. T(!_sfetch_ring_full(&ring));
  251. _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1));
  252. }
  253. T(_sfetch_ring_full(&ring));
  254. for (i = 0; i < 4; i++) {
  255. T(_sfetch_ring_dequeue(&ring) == _sfetch_make_id(1, (i + 61)));
  256. }
  257. T(_sfetch_ring_empty(&ring));
  258. _sfetch_ring_discard(&ring);
  259. sfetch_shutdown();
  260. }
  261. UTEST(sokol_fetch, ring_wrap_count) {
  262. sfetch_setup(&(sfetch_desc_t){0});
  263. _sfetch_ring_t ring = {0};
  264. _sfetch_ring_init(&ring, 8);
  265. // add and remove 4 items to move tail to the middle
  266. for (uint32_t i = 0; i < 4; i++) {
  267. _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1));
  268. _sfetch_ring_dequeue(&ring);
  269. T(_sfetch_ring_empty(&ring));
  270. }
  271. // add another 8 items
  272. for (uint32_t i = 0; i < 8; i++) {
  273. _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1));
  274. }
  275. // now test, dequeue and test...
  276. T(_sfetch_ring_full(&ring));
  277. for (uint32_t i = 0; i < 8; i++) {
  278. T(_sfetch_ring_count(&ring) == (8 - i));
  279. _sfetch_ring_dequeue(&ring);
  280. }
  281. T(_sfetch_ring_count(&ring) == 0);
  282. T(_sfetch_ring_empty(&ring));
  283. _sfetch_ring_discard(&ring);
  284. sfetch_shutdown();
  285. }
  286. /* NOTE: channel_worker is called from a thread */
  287. static int num_processed_items = 0;
  288. static void channel_worker(_sfetch_t* ctx, uint32_t slot_id) {
  289. (void)ctx;
  290. (void)slot_id;
  291. num_processed_items++;
  292. }
  293. UTEST(sokol_fetch, channel_init_discard) {
  294. sfetch_setup(&(sfetch_desc_t){0});
  295. num_processed_items = 0;
  296. _sfetch_channel_t chn = {0};
  297. const uint32_t num_slots = 12;
  298. const uint32_t num_lanes = 64;
  299. _sfetch_channel_init(&chn, 0, num_slots, num_lanes, channel_worker);
  300. T(chn.valid);
  301. T(_sfetch_ring_full(&chn.free_lanes));
  302. T(_sfetch_ring_empty(&chn.user_sent));
  303. T(_sfetch_ring_empty(&chn.user_incoming));
  304. #if !defined(__EMSCRIPTEN__)
  305. T(_sfetch_ring_empty(&chn.thread_incoming));
  306. T(_sfetch_ring_empty(&chn.thread_outgoing));
  307. #endif
  308. T(_sfetch_ring_empty(&chn.user_outgoing));
  309. _sfetch_channel_discard(&chn);
  310. T(!chn.valid);
  311. sfetch_shutdown();
  312. }
  313. /* public API functions */
  314. UTEST(sokol_fetch, setup_shutdown) {
  315. sfetch_setup(&(sfetch_desc_t){0});
  316. T(sfetch_valid());
  317. // check default values
  318. T(sfetch_desc().max_requests == 128);
  319. T(sfetch_desc().num_channels == 1);
  320. T(sfetch_desc().num_lanes == 1);
  321. sfetch_shutdown();
  322. T(!sfetch_valid());
  323. }
  324. UTEST(sokol_fetch, setup_too_many_channels) {
  325. /* try to initialize with too many channels, this should clamp to
  326. SFETCH_MAX_CHANNELS
  327. */
  328. sfetch_setup(&(sfetch_desc_t){
  329. .num_channels = 64
  330. });
  331. T(sfetch_valid());
  332. T(sfetch_desc().num_channels == SFETCH_MAX_CHANNELS);
  333. sfetch_shutdown();
  334. }
  335. UTEST(sokol_fetch, max_path) {
  336. T(sfetch_max_path() == SFETCH_MAX_PATH);
  337. }
  338. UTEST(sokol_fetch, max_userdata) {
  339. T(sfetch_max_userdata_bytes() == (SFETCH_MAX_USERDATA_UINT64 * sizeof(uint64_t)));
  340. }
  341. static uint8_t fail_open_buffer[128];
  342. static bool fail_open_passed;
  343. static void fail_open_callback(const sfetch_response_t* response) {
  344. /* if opening a file fails, it will immediate switch into CLOSED state */
  345. if ((response->failed) && (response->error_code == SFETCH_ERROR_FILE_NOT_FOUND)) {
  346. fail_open_passed = true;
  347. }
  348. }
  349. UTEST(sokol_fetch, fail_open) {
  350. sfetch_setup(&(sfetch_desc_t){0});
  351. sfetch_handle_t h = sfetch_send(&(sfetch_request_t){
  352. .path = "non_existing_file.txt",
  353. .callback = fail_open_callback,
  354. .buffer = SFETCH_RANGE(fail_open_buffer),
  355. });
  356. fail_open_passed = false;
  357. int frame_count = 0;
  358. const int max_frames = 10000;
  359. while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) {
  360. sfetch_dowork();
  361. sleep_ms(1);
  362. }
  363. T(frame_count < max_frames);
  364. T(fail_open_passed);
  365. sfetch_shutdown();
  366. }
  367. static bool load_file_fixed_buffer_passed;
  368. // The file callback is called from the "current user thread" (the same
  369. // thread where the sfetch_send() for this request was called). Note that you
  370. // can call sfetch_setup/shutdown() on multiple threads, each thread will
  371. // get its own thread-local "sokol-fetch instance" and its own set of
  372. // IO-channel threads.
  373. static void load_file_fixed_buffer_callback(const sfetch_response_t* response) {
  374. // when loading the whole file at once, the fetched state
  375. // is the best place to grab/process the data
  376. if (response->fetched) {
  377. if ((response->data_offset == 0) &&
  378. (response->data.ptr == load_file_buf) &&
  379. (response->data.size == combatsignal_file_size) &&
  380. (response->buffer.ptr == load_file_buf) &&
  381. (response->buffer.size == sizeof(load_file_buf)) &&
  382. response->finished)
  383. {
  384. load_file_fixed_buffer_passed = true;
  385. }
  386. }
  387. }
  388. UTEST(sokol_fetch, load_file_fixed_buffer) {
  389. memset(load_file_buf, 0, sizeof(load_file_buf));
  390. sfetch_setup(&(sfetch_desc_t){0});
  391. // send a load-request for a file where we know the max size upfront,
  392. // so we can provide a buffer right in the fetch request (otherwise
  393. // the buffer needs to be provided in the callback when the request
  394. // is in OPENED state, since only then the file size will be known).
  395. sfetch_handle_t h = sfetch_send(&(sfetch_request_t){
  396. .path = "comsi.s3m",
  397. .callback = load_file_fixed_buffer_callback,
  398. .buffer = SFETCH_RANGE(load_file_buf),
  399. });
  400. // simulate a frame-loop for as long as the request is in flight, normally
  401. // the sfetch_dowork() function is just called somewhere in the frame
  402. // to pump messages in and out of the IO threads, and invoke user-callbacks
  403. int frame_count = 0;
  404. const int max_frames = 10000;
  405. while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) {
  406. sfetch_dowork();
  407. sleep_ms(1);
  408. }
  409. T(frame_count < max_frames);
  410. T(load_file_fixed_buffer_passed);
  411. sfetch_shutdown();
  412. }
  413. /* tests whether files with unknown size are processed correctly */
  414. static bool load_file_unknown_size_opened_passed;
  415. static bool load_file_unknown_size_fetched_passed;
  416. static void load_file_unknown_size_callback(const sfetch_response_t* response) {
  417. if (response->dispatched) {
  418. if ((response->data_offset == 0) &&
  419. (response->data.ptr == 0) &&
  420. (response->data.size == 0) &&
  421. (response->buffer.ptr == 0) &&
  422. (response->buffer.size == 0) &&
  423. !response->finished)
  424. {
  425. load_file_unknown_size_opened_passed = true;
  426. sfetch_bind_buffer(response->handle, SFETCH_RANGE(load_file_buf));
  427. }
  428. }
  429. else if (response->fetched) {
  430. if ((response->data_offset == 0) &&
  431. (response->data.ptr == load_file_buf) &&
  432. (response->data.size == combatsignal_file_size) &&
  433. (response->buffer.ptr == load_file_buf) &&
  434. (response->buffer.size == sizeof(load_file_buf)) &&
  435. response->finished)
  436. {
  437. load_file_unknown_size_fetched_passed = true;
  438. }
  439. }
  440. }
  441. UTEST(sokol_fetch, load_file_unknown_size) {
  442. memset(load_file_buf, 0, sizeof(load_file_buf));
  443. sfetch_setup(&(sfetch_desc_t){0});
  444. sfetch_handle_t h = sfetch_send(&(sfetch_request_t){
  445. .path = "comsi.s3m",
  446. .callback = load_file_unknown_size_callback
  447. });
  448. int frame_count = 0;
  449. const int max_frames = 10000;
  450. while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) {
  451. sfetch_dowork();
  452. sleep_ms(1);
  453. }
  454. T(frame_count < max_frames);
  455. T(load_file_unknown_size_opened_passed);
  456. T(load_file_unknown_size_fetched_passed);
  457. sfetch_shutdown();
  458. }
  459. /* tests whether not providing a buffer in OPENED properly fails */
  460. static bool load_file_no_buffer_opened_passed;
  461. static bool load_file_no_buffer_failed_passed;
  462. static void load_file_no_buffer_callback(const sfetch_response_t* response) {
  463. if (response->dispatched) {
  464. if ((response->data_offset == 0) &&
  465. (response->data.ptr == 0) &&
  466. (response->data.size == 0) &&
  467. (response->buffer.ptr == 0) &&
  468. (response->buffer.size == 0) &&
  469. !response->finished)
  470. {
  471. /* DO NOT provide a buffer here, see if that properly fails */
  472. load_file_no_buffer_opened_passed = true;
  473. }
  474. }
  475. else if ((response->failed) && (response->error_code == SFETCH_ERROR_NO_BUFFER)) {
  476. if (load_file_no_buffer_opened_passed) {
  477. load_file_no_buffer_failed_passed = true;
  478. }
  479. }
  480. }
  481. UTEST(sokol_fetch, load_file_no_buffer) {
  482. memset(load_file_buf, 0, sizeof(load_file_buf));
  483. sfetch_setup(&(sfetch_desc_t){0});
  484. sfetch_handle_t h = sfetch_send(&(sfetch_request_t){
  485. .path = "comsi.s3m",
  486. .callback = load_file_no_buffer_callback
  487. });
  488. int frame_count = 0;
  489. const int max_frames = 10000;
  490. while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) {
  491. sfetch_dowork();
  492. sleep_ms(1);
  493. }
  494. T(frame_count < max_frames);
  495. T(load_file_no_buffer_opened_passed);
  496. T(load_file_no_buffer_failed_passed);
  497. sfetch_shutdown();
  498. }
  499. static bool load_file_too_small_passed;
  500. static uint8_t load_file_too_small_buf[8192];
  501. static void load_file_too_small_callback(const sfetch_response_t* response) {
  502. if (response->failed && (response->error_code == SFETCH_ERROR_BUFFER_TOO_SMALL)) {
  503. load_file_too_small_passed = true;
  504. }
  505. }
  506. UTEST(sokol_fetch, load_file_too_small_buffer) {
  507. memset(load_file_buf, 0, sizeof(load_file_buf));
  508. sfetch_setup(&(sfetch_desc_t){0});
  509. sfetch_handle_t h = sfetch_send(&(sfetch_request_t){
  510. .path = "comsi.s3m",
  511. .callback = load_file_too_small_callback,
  512. .buffer = SFETCH_RANGE(load_file_too_small_buf),
  513. });
  514. int frame_count = 0;
  515. const int max_frames = 10000;
  516. while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) {
  517. sfetch_dowork();
  518. sleep_ms(1);
  519. }
  520. T(frame_count < max_frames);
  521. T(load_file_too_small_passed);
  522. sfetch_shutdown();
  523. }
  524. /* test loading a big file via a small chunk-buffer, the callback will
  525. be called multiple times with the FETCHED state until the entire file
  526. is loaded
  527. */
  528. static bool load_file_chunked_passed;
  529. static uint8_t load_chunk_buf[8192];
  530. static uint8_t load_file_chunked_content[500000];
  531. static void load_file_chunked_callback(const sfetch_response_t* response) {
  532. if (response->fetched) {
  533. uint8_t* dst = &load_file_chunked_content[response->data_offset];
  534. const uint8_t* src = response->data.ptr;
  535. size_t num_bytes = response->data.size;
  536. memcpy(dst, src, num_bytes);
  537. if (response->finished) {
  538. load_file_chunked_passed = true;
  539. }
  540. }
  541. }
  542. UTEST(sokol_fetch, load_file_chunked) {
  543. memset(load_file_buf, 0, sizeof(load_file_buf));
  544. memset(load_chunk_buf, 0, sizeof(load_chunk_buf));
  545. memset(load_file_chunked_content, 0, sizeof(load_file_chunked_content));
  546. load_file_fixed_buffer_passed = false;
  547. sfetch_setup(&(sfetch_desc_t){0});
  548. // request for chunked-loading
  549. sfetch_handle_t h0 = sfetch_send(&(sfetch_request_t){
  550. .path = "comsi.s3m",
  551. .callback = load_file_chunked_callback,
  552. .buffer = SFETCH_RANGE(load_chunk_buf),
  553. .chunk_size = sizeof(load_chunk_buf)
  554. });
  555. // request for all-in-one loading for comparing with the chunked buffer
  556. sfetch_handle_t h1 = sfetch_send(&(sfetch_request_t){
  557. .path = "comsi.s3m",
  558. .callback = load_file_fixed_buffer_callback,
  559. .buffer = SFETCH_RANGE(load_file_buf),
  560. });
  561. int frame_count = 0;
  562. const int max_frames = 10000;
  563. while ((sfetch_handle_valid(h0) || sfetch_handle_valid(h1)) && (frame_count++ < max_frames)) {
  564. sfetch_dowork();
  565. sleep_ms(1);
  566. }
  567. T(frame_count < max_frames);
  568. T(load_file_chunked_passed);
  569. T(0 == memcmp(load_file_chunked_content, load_file_buf, combatsignal_file_size));
  570. sfetch_shutdown();
  571. }
  572. /* load N big files in small chunks interleaved on the same channel via lanes */
  573. #define LOAD_FILE_LANES_NUM_LANES (4)
  574. static uint8_t load_file_lanes_chunk_buf[LOAD_FILE_LANES_NUM_LANES][8192];
  575. static uint8_t load_file_lanes_content[LOAD_FILE_LANES_NUM_LANES][500000];
  576. static int load_file_lanes_passed[LOAD_FILE_LANES_NUM_LANES];
  577. static void load_file_lanes_callback(const sfetch_response_t* response) {
  578. assert((response->channel == 0) && (response->lane < LOAD_FILE_LANES_NUM_LANES));
  579. if (response->fetched) {
  580. uint8_t* dst = &load_file_lanes_content[response->lane][response->data_offset];
  581. const uint8_t* src = response->data.ptr;
  582. size_t num_bytes = response->data.size;
  583. memcpy(dst, src, num_bytes);
  584. if (response->finished) {
  585. load_file_lanes_passed[response->lane]++;
  586. }
  587. }
  588. }
  589. UTEST(sokol_fetch, load_file_lanes) {
  590. for (int i = 0; i < LOAD_FILE_LANES_NUM_LANES; i++) {
  591. memset(load_file_lanes_content[i], i, sizeof(load_file_lanes_content[i]));
  592. }
  593. sfetch_setup(&(sfetch_desc_t){
  594. .num_channels = 1,
  595. .num_lanes = LOAD_FILE_LANES_NUM_LANES,
  596. });
  597. sfetch_handle_t h[LOAD_FILE_LANES_NUM_LANES];
  598. for (int lane = 0; lane < LOAD_FILE_LANES_NUM_LANES; lane++) {
  599. h[lane] = sfetch_send(&(sfetch_request_t){
  600. .path = "comsi.s3m",
  601. .callback = load_file_lanes_callback,
  602. .buffer = { .ptr = load_file_lanes_chunk_buf[lane], .size = sizeof(load_file_lanes_chunk_buf[0]) },
  603. .chunk_size = sizeof(load_file_lanes_chunk_buf[0])
  604. });
  605. }
  606. bool done = false;
  607. int frame_count = 0;
  608. const int max_frames = 10000;
  609. while (!done && (frame_count++ < max_frames)) {
  610. done = true;
  611. for (int i = 0; i < LOAD_FILE_LANES_NUM_LANES; i++) {
  612. done &= !sfetch_handle_valid(h[i]);
  613. }
  614. sfetch_dowork();
  615. sleep_ms(1);
  616. }
  617. T(frame_count < max_frames);
  618. for (int i = 0; i < LOAD_FILE_LANES_NUM_LANES; i++) {
  619. T(1 == load_file_lanes_passed[i]);
  620. T(0 == memcmp(load_file_lanes_content[0], load_file_lanes_content[i], combatsignal_file_size));
  621. }
  622. sfetch_shutdown();
  623. }
  624. /* same as above, but issue more requests than available lanes to test if rate-limiting works */
  625. #define LOAD_FILE_THROTTLE_NUM_LANES (4)
  626. #define LOAD_FILE_THROTTLE_NUM_PASSES (3)
  627. #define LOAD_FILE_THROTTLE_NUM_REQUESTS (12) // lanes * passes
  628. static uint8_t load_file_throttle_chunk_buf[LOAD_FILE_THROTTLE_NUM_LANES][128000];
  629. static uint8_t load_file_throttle_content[LOAD_FILE_THROTTLE_NUM_PASSES][LOAD_FILE_THROTTLE_NUM_LANES][500000];
  630. static int load_file_throttle_passed[LOAD_FILE_THROTTLE_NUM_LANES];
  631. static void load_file_throttle_callback(const sfetch_response_t* response) {
  632. assert((response->channel == 0) && (response->lane < LOAD_FILE_LANES_NUM_LANES));
  633. if (response->fetched) {
  634. assert(load_file_throttle_passed[response->lane] < LOAD_FILE_THROTTLE_NUM_PASSES);
  635. uint8_t* dst = &load_file_throttle_content[load_file_throttle_passed[response->lane]][response->lane][response->data_offset];
  636. const uint8_t* src = response->data.ptr;
  637. size_t num_bytes = response->data.size;
  638. memcpy(dst, src, num_bytes);
  639. if (response->finished) {
  640. load_file_throttle_passed[response->lane]++;
  641. }
  642. }
  643. }
  644. UTEST(sokol_fetch, load_file_throttle) {
  645. for (int pass = 0; pass < LOAD_FILE_THROTTLE_NUM_PASSES; pass++) {
  646. for (int lane = 0; lane < LOAD_FILE_THROTTLE_NUM_LANES; lane++) {
  647. memset(load_file_throttle_content[pass][lane], 10*pass+lane, sizeof(load_file_throttle_content[pass][lane]));
  648. }
  649. }
  650. sfetch_setup(&(sfetch_desc_t){
  651. .num_channels = 1,
  652. .num_lanes = LOAD_FILE_THROTTLE_NUM_LANES,
  653. });
  654. sfetch_handle_t h[LOAD_FILE_THROTTLE_NUM_REQUESTS];
  655. for (int i = 0; i < LOAD_FILE_THROTTLE_NUM_REQUESTS; i++) {
  656. h[i] = sfetch_send(&(sfetch_request_t){
  657. .path = "comsi.s3m",
  658. .callback = load_file_throttle_callback,
  659. .buffer = {
  660. .ptr = load_file_throttle_chunk_buf[i % LOAD_FILE_THROTTLE_NUM_LANES],
  661. .size = sizeof(load_file_throttle_chunk_buf[0]),
  662. },
  663. .chunk_size = sizeof(load_file_throttle_chunk_buf[0])
  664. });
  665. T(sfetch_handle_valid(h[i]));
  666. }
  667. bool done = false;
  668. int frame_count = 0;
  669. const int max_frames = 10000;
  670. while (!done && (frame_count++ < max_frames)) {
  671. done = true;
  672. for (int i = 0; i < LOAD_FILE_THROTTLE_NUM_REQUESTS; i++) {
  673. done &= !sfetch_handle_valid(h[i]);
  674. }
  675. sfetch_dowork();
  676. sleep_ms(1);
  677. }
  678. T(frame_count < max_frames);
  679. for (int lane = 0; lane < LOAD_FILE_THROTTLE_NUM_LANES; lane++) {
  680. T(LOAD_FILE_THROTTLE_NUM_PASSES == load_file_throttle_passed[lane]);
  681. for (int pass = 0; pass < LOAD_FILE_THROTTLE_NUM_PASSES; pass++) {
  682. T(0 == memcmp(load_file_throttle_content[0][0], load_file_throttle_content[pass][lane], combatsignal_file_size));
  683. }
  684. }
  685. sfetch_shutdown();
  686. }
  687. /* test parallel fetches on multiple channels */
  688. #define LOAD_CHANNEL_NUM_CHANNELS (16)
  689. static uint8_t load_channel_buf[LOAD_CHANNEL_NUM_CHANNELS][500000];
  690. static bool load_channel_passed[LOAD_CHANNEL_NUM_CHANNELS];
  691. void load_channel_callback(const sfetch_response_t* response) {
  692. assert(response->channel < LOAD_CHANNEL_NUM_CHANNELS);
  693. assert(!load_channel_passed[response->channel]);
  694. if (response->fetched) {
  695. if ((response->data.size == combatsignal_file_size) && response->finished) {
  696. load_channel_passed[response->channel] = true;
  697. }
  698. }
  699. }
  700. UTEST(sokol_fetch, load_channel) {
  701. for (int chn = 0; chn < LOAD_CHANNEL_NUM_CHANNELS; chn++) {
  702. memset(load_channel_buf[chn], chn, sizeof(load_channel_buf[chn]));
  703. }
  704. sfetch_setup(&(sfetch_desc_t){
  705. .num_channels = LOAD_CHANNEL_NUM_CHANNELS
  706. });
  707. sfetch_handle_t h[LOAD_CHANNEL_NUM_CHANNELS];
  708. for (uint32_t chn = 0; chn < LOAD_CHANNEL_NUM_CHANNELS; chn++) {
  709. h[chn] = sfetch_send(&(sfetch_request_t){
  710. .path = "comsi.s3m",
  711. .channel = chn,
  712. .callback = load_channel_callback,
  713. .buffer = SFETCH_RANGE(load_channel_buf[chn]),
  714. });
  715. }
  716. bool done = false;
  717. int frame_count = 0;
  718. const int max_frames = 100000;
  719. while (!done && (frame_count++ < max_frames)) {
  720. done = true;
  721. for (int i = 0; i < LOAD_CHANNEL_NUM_CHANNELS; i++) {
  722. done &= !sfetch_handle_valid(h[i]);
  723. }
  724. sfetch_dowork();
  725. sleep_ms(1);
  726. }
  727. T(frame_count < max_frames);
  728. for (int chn = 0; chn < LOAD_CHANNEL_NUM_CHANNELS; chn++) {
  729. T(load_channel_passed[chn]);
  730. T(0 == memcmp(load_channel_buf[0], load_channel_buf[chn], combatsignal_file_size));
  731. }
  732. sfetch_shutdown();
  733. }
  734. static bool load_file_cancel_passed = false;
  735. static void load_file_cancel_callback(const sfetch_response_t* response) {
  736. if (response->dispatched) {
  737. sfetch_cancel(response->handle);
  738. }
  739. if (response->cancelled && response->finished && response->failed && (response->error_code == SFETCH_ERROR_CANCELLED)) {
  740. load_file_cancel_passed = true;
  741. }
  742. }
  743. UTEST(sokol_fetch, load_file_cancel) {
  744. sfetch_setup(&(sfetch_desc_t){
  745. .num_channels = 1
  746. });
  747. sfetch_handle_t h = sfetch_send(&(sfetch_request_t){
  748. .path = "comsi.s3m",
  749. .callback = load_file_cancel_callback,
  750. });
  751. int frame_count = 0;
  752. const int max_frames = 10000;
  753. while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) {
  754. sfetch_dowork();
  755. sleep_ms(1);
  756. }
  757. T(frame_count < max_frames);
  758. T(load_file_cancel_passed);
  759. sfetch_shutdown();
  760. }
  761. static bool load_file_cancel_before_dispatch_passed = false;
  762. static void load_file_cancel_before_dispatch_callback(const sfetch_response_t* response) {
  763. // cancelled, finished, failed and error code must all be set
  764. if (response->cancelled && response->finished && response->failed && (response->error_code == SFETCH_ERROR_CANCELLED)) {
  765. load_file_cancel_before_dispatch_passed = true;
  766. }
  767. }
  768. UTEST(sokol_fetch, load_file_cancel_before_dispatch) {
  769. sfetch_setup(&(sfetch_desc_t){
  770. .num_channels = 1,
  771. });
  772. sfetch_handle_t h = sfetch_send(&(sfetch_request_t){
  773. .path = "comsi.s3m",
  774. .callback = load_file_cancel_before_dispatch_callback,
  775. });
  776. sfetch_cancel(h);
  777. sfetch_dowork();
  778. T(load_file_cancel_before_dispatch_passed);
  779. sfetch_shutdown();
  780. }
  781. static bool load_file_cancel_after_dispatch_passed = false;
  782. static void load_file_cancel_after_dispatch_callback(const sfetch_response_t* response) {
  783. // when cancelled, then finished, failed and error code must all be set
  784. if (response->cancelled && response->finished && response->failed && (response->error_code == SFETCH_ERROR_CANCELLED)) {
  785. load_file_cancel_after_dispatch_passed = true;
  786. }
  787. }
  788. UTEST(sokol_fetch, load_file_cancel_after_dispatch) {
  789. sfetch_setup(&(sfetch_desc_t){
  790. .num_channels = 1,
  791. });
  792. sfetch_handle_t h = sfetch_send(&(sfetch_request_t){
  793. .path = "comsi.s3m",
  794. .callback = load_file_cancel_after_dispatch_callback,
  795. .buffer = SFETCH_RANGE(load_file_buf),
  796. });
  797. int frame_count = 0;
  798. const int max_frames = 10000;
  799. while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) {
  800. sfetch_dowork();
  801. sfetch_cancel(h);
  802. sleep_ms(1);
  803. }
  804. T(frame_count < max_frames);
  805. T(load_file_cancel_after_dispatch_passed);
  806. sfetch_shutdown();
  807. }