Skip to content

Commit 88393de

Browse files
authored
Merge pull request #14 from sandrwich/feat/async-tle-pull
Async TLE pull to prevent UI freeze during download
2 parents e1c25c1 + 05d158e commit 88393de

File tree

1 file changed

+111
-35
lines changed

1 file changed

+111
-35
lines changed

src/ui.c

Lines changed: 111 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ typedef struct tagMSG *LPMSG;
2323

2424
//TODO: explain better what in gods name happened above
2525

26+
#if defined(_WIN32) || defined(_WIN64)
27+
#include <process.h>
28+
#else
29+
#include <pthread.h>
30+
#endif
31+
2632
#define RAYGUI_IMPLEMENTATION
2733
#include "../lib/raygui.h"
2834

@@ -156,6 +162,16 @@ static bool manual_expanded = true;
156162
static bool celestrak_selected[25] = {false};
157163
static long data_tle_epoch = -1;
158164

165+
enum { PULL_IDLE = 0, PULL_BUSY, PULL_DONE, PULL_ERROR };
166+
static volatile int pull_state = PULL_IDLE;
167+
static volatile bool pull_partial = false;
168+
static AppConfig *pull_cfg = NULL;
169+
#if defined(_WIN32) || defined(_WIN64)
170+
static HANDLE pull_thread = NULL;
171+
#else
172+
static pthread_t pull_thread;
173+
#endif
174+
159175
static char new_tle_buf[512] = "";
160176
static bool edit_new_tle = false;
161177

@@ -312,9 +328,9 @@ static size_t write_memory_callback(void *contents, size_t size, size_t nmemb, v
312328
return realsize;
313329
}
314330

315-
static void DownloadTLESource(CURL *curl, const char *url, FILE *out)
331+
static bool DownloadTLESource(CURL *curl, const char *url, FILE *out)
316332
{
317-
if (!curl || !url || !out) return;
333+
if (!curl || !url || !out) return false;
318334

319335
struct MemoryStruct chunk;
320336
chunk.memory = malloc(1);
@@ -324,7 +340,7 @@ static void DownloadTLESource(CURL *curl, const char *url, FILE *out)
324340
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
325341
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_memory_callback);
326342
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
327-
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); // handle compression
343+
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); /* handle compression */
328344
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla 5.0 (compatible; TLEscope/3.X; +https://github.com/aweeri/TLEscope)"); //TODO: add version whenever aval internally
329345

330346
#if defined(_WIN32) || defined(_WIN64)
@@ -335,7 +351,8 @@ static void DownloadTLESource(CURL *curl, const char *url, FILE *out)
335351
long http_code = 0;
336352
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
337353

338-
if (res == CURLE_OK && http_code == 200)
354+
bool ok = (res == CURLE_OK && http_code == 200);
355+
if (ok)
339356
{
340357
fwrite(chunk.memory, 1, chunk.size, out);
341358
fprintf(out, "\r\n");
@@ -346,6 +363,7 @@ static void DownloadTLESource(CURL *curl, const char *url, FILE *out)
346363
}
347364

348365
free(chunk.memory);
366+
return ok;
349367
}
350368

351369
static void ReloadTLEsLocally(UIContext *ctx, AppConfig *cfg)
@@ -366,45 +384,89 @@ static void ReloadTLEsLocally(UIContext *ctx, AppConfig *cfg)
366384
LoadSatSelection();
367385
}
368386

369-
static void PullTLEData(UIContext *ctx, AppConfig *cfg)
387+
/* background thread: downloads all selected TLE sources to data.tle */
388+
static void *PullTLEThread(void *arg)
370389
{
390+
(void)arg;
391+
AppConfig *cfg = pull_cfg;
392+
371393
FILE *out = fopen("data.tle", "wb");
372-
if (out)
394+
if (!out)
395+
{
396+
pull_state = PULL_ERROR;
397+
return NULL;
398+
}
399+
400+
unsigned int mask = 0, ret_mask = 0, cust_mask = 0;
401+
for (int i = 0; i < 25; i++)
402+
if (celestrak_selected[i]) mask |= (1 << i);
403+
for (int i = 0; i < NUM_RETLECTOR_SOURCES; i++)
404+
if (retlector_selected[i]) ret_mask |= (1 << i);
405+
for (int i = 0; i < cfg->custom_tle_source_count; i++)
406+
if (cfg->custom_tle_sources[i].selected) cust_mask |= (1 << i);
407+
408+
fprintf(out, "# EPOCH:%ld MASK:%u CUST_MASK:%u RET_MASK:%u\r\n", (long)time(NULL), mask, cust_mask, ret_mask);
409+
410+
int ok_count = 0, fail_count = 0;
411+
CURL *curl = curl_easy_init();
412+
if (curl)
373413
{
374-
unsigned int mask = 0, ret_mask = 0, cust_mask = 0;
375-
for (int i = 0; i < 25; i++)
376-
if (celestrak_selected[i]) mask |= (1 << i);
377414
for (int i = 0; i < NUM_RETLECTOR_SOURCES; i++)
378-
if (retlector_selected[i]) ret_mask |= (1 << i);
415+
if (ret_mask & (1 << i))
416+
{ if (DownloadTLESource(curl, RETLECTOR_SOURCES[i].url, out)) ok_count++; else fail_count++; }
417+
418+
for (int i = 0; i < 25; i++)
419+
if (mask & (1 << i))
420+
{ if (DownloadTLESource(curl, SOURCES[i].url, out)) ok_count++; else fail_count++; }
421+
379422
for (int i = 0; i < cfg->custom_tle_source_count; i++)
380-
if (cfg->custom_tle_sources[i].selected) cust_mask |= (1 << i);
423+
if (cust_mask & (1 << i))
424+
{ if (DownloadTLESource(curl, cfg->custom_tle_sources[i].url, out)) ok_count++; else fail_count++; }
381425

382-
fprintf(out, "# EPOCH:%ld MASK:%u CUST_MASK:%u RET_MASK:%u\r\n", (long)time(NULL), mask, cust_mask, ret_mask);
426+
curl_easy_cleanup(curl);
427+
}
428+
else
429+
{
430+
printf("Failed to initialize libcurl.\n");
431+
}
383432

384-
CURL *curl = curl_easy_init();
385-
if (curl)
386-
{
387-
for (int i = 0; i < NUM_RETLECTOR_SOURCES; i++)
388-
if (retlector_selected[i])
389-
DownloadTLESource(curl, RETLECTOR_SOURCES[i].url, out);
433+
fclose(out);
390434

391-
for (int i = 0; i < 25; i++)
392-
if (celestrak_selected[i])
393-
DownloadTLESource(curl, SOURCES[i].url, out);
435+
pull_partial = (ok_count > 0 && fail_count > 0);
436+
__sync_synchronize(); /* ensure pull_partial is visible before pull_state on ARM */
437+
if (fail_count > 0 && ok_count == 0) pull_state = PULL_ERROR;
438+
else pull_state = PULL_DONE;
439+
return NULL;
440+
}
394441

395-
for (int i = 0; i < cfg->custom_tle_source_count; i++)
396-
if (cfg->custom_tle_sources[i].selected)
397-
DownloadTLESource(curl, cfg->custom_tle_sources[i].url, out);
442+
#if defined(_WIN32) || defined(_WIN64)
443+
static void PullTLEThreadWin(void *arg) { PullTLEThread(arg); }
444+
#endif
398445

399-
curl_easy_cleanup(curl);
400-
}
401-
else
402-
{
403-
printf("Failed to initialize libcurl.\n");
404-
}
446+
/* called from main thread to kick off async pull */
447+
static void PullTLEData(AppConfig *cfg)
448+
{
449+
if (pull_state == PULL_BUSY) return;
450+
pull_state = PULL_BUSY;
451+
pull_partial = false;
452+
pull_cfg = cfg;
405453

406-
fclose(out);
454+
#if defined(_WIN32) || defined(_WIN64)
455+
uintptr_t h = _beginthread(PullTLEThreadWin, 0, NULL);
456+
if (h == (uintptr_t)-1L) { pull_state = PULL_ERROR; return; }
457+
pull_thread = (HANDLE)h;
458+
#else
459+
if (pthread_create(&pull_thread, NULL, PullTLEThread, NULL) != 0)
460+
{ pull_state = PULL_ERROR; return; }
461+
pthread_detach(pull_thread);
462+
#endif
463+
}
407464

465+
/* called each frame from DrawGUI to finish reload on the main thread */
466+
static void FinishPullIfDone(UIContext *ctx, AppConfig *cfg)
467+
{
468+
if (pull_state == PULL_DONE)
469+
{
408470
if (ctx)
409471
{
410472
*ctx->selected_sat = NULL;
@@ -425,6 +487,7 @@ static void PullTLEData(UIContext *ctx, AppConfig *cfg)
425487
}
426488
LoadSatSelection();
427489
data_tle_epoch = time(NULL);
490+
pull_state = PULL_IDLE;
428491
}
429492
}
430493

@@ -969,6 +1032,8 @@ static bool DrawMaterialWindow(Rectangle bounds, const char *title, AppConfig *c
9691032
/* main ui rendering loop */
9701033
void DrawGUI(UIContext *ctx, AppConfig *cfg, Font customFont)
9711034
{
1035+
FinishPullIfDone(ctx, cfg);
1036+
9721037
*ctx->show_scope = show_scope_dialog;
9731038
*ctx->scope_az = scope_az;
9741039
*ctx->scope_el = scope_el;
@@ -1600,9 +1665,17 @@ void DrawGUI(UIContext *ctx, AppConfig *cfg, Font customFont)
16001665
}
16011666
DrawUIText(customFont, age_str, tm_x + 10 * cfg->ui_scale, tm_y + 35 * cfg->ui_scale, 16 * cfg->ui_scale, cfg->text_main);
16021667

1603-
if (GuiButton((Rectangle){tm_x + tmMgrWindow.width - 110 * cfg->ui_scale, tm_y + 30 * cfg->ui_scale, 100 * cfg->ui_scale, 26 * cfg->ui_scale}, "Apply"))
16041668
{
1605-
PullTLEData(ctx, cfg);
1669+
const char *btn_label = "Apply";
1670+
if (pull_state == PULL_BUSY) btn_label = "Pulling..";
1671+
else if (pull_state == PULL_ERROR) btn_label = "Error";
1672+
else if (pull_partial) btn_label = "Partial";
1673+
if (pull_state == PULL_BUSY) GuiDisable();
1674+
if (GuiButton((Rectangle){tm_x + tmMgrWindow.width - 110 * cfg->ui_scale, tm_y + 30 * cfg->ui_scale, 100 * cfg->ui_scale, 26 * cfg->ui_scale}, btn_label))
1675+
{
1676+
PullTLEData(cfg);
1677+
}
1678+
if (pull_state == PULL_BUSY) GuiEnable();
16061679
}
16071680

16081681
float total_height = 28 * cfg->ui_scale + (retlector_expanded ? NUM_RETLECTOR_SOURCES * 25 * cfg->ui_scale : 0);
@@ -3213,10 +3286,13 @@ case WND_SCOPE:
32133286
float spacing = 15 * cfg->ui_scale;
32143287
float startX = tleWarnWindow.x + (tleWarnWindow.width - (3 * btnWidth + 2 * spacing)) / 2.0f;
32153288

3216-
if (GuiButton((Rectangle){startX, tleWarnWindow.y + 105 * cfg->ui_scale, btnWidth, 35 * cfg->ui_scale}, "#112# Update All")) {
3217-
PullTLEData(ctx, cfg);
3289+
if (pull_state == PULL_BUSY) GuiDisable();
3290+
if (GuiButton((Rectangle){startX, tleWarnWindow.y + 105 * cfg->ui_scale, btnWidth, 35 * cfg->ui_scale},
3291+
pull_state == PULL_BUSY ? "Pulling.." : "#112# Update All")) {
3292+
PullTLEData(cfg);
32183293
show_tle_warning = false;
32193294
}
3295+
if (pull_state == PULL_BUSY) GuiEnable();
32203296
if (GuiButton((Rectangle){startX + btnWidth + spacing, tleWarnWindow.y + 105 * cfg->ui_scale, btnWidth, 35 * cfg->ui_scale}, "#1# Manage")) {
32213297
show_tle_warning = false;
32223298
if (!show_tle_mgr_dialog) {

0 commit comments

Comments
 (0)