Skip to content

Commit 7f2922b

Browse files
committed
async TLE pull to prevent UI freeze during download
Move TLE downloads to a background pthread so the render loop stays responsive. Button shows Pulling.. while active, Error if all sources fail, Partial if some fail. Reload happens on the main thread once the download thread finishes.
1 parent a0761f6 commit 7f2922b

File tree

1 file changed

+110
-35
lines changed

1 file changed

+110
-35
lines changed

src/ui.c

Lines changed: 110 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

@@ -311,9 +327,9 @@ static size_t write_memory_callback(void *contents, size_t size, size_t nmemb, v
311327
return realsize;
312328
}
313329

314-
static void DownloadTLESource(CURL *curl, const char *url, FILE *out)
330+
static bool DownloadTLESource(CURL *curl, const char *url, FILE *out)
315331
{
316-
if (!curl || !url || !out) return;
332+
if (!curl || !url || !out) return false;
317333

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

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

337-
if (res == CURLE_OK && http_code == 200)
353+
bool ok = (res == CURLE_OK && http_code == 200);
354+
if (ok)
338355
{
339356
fwrite(chunk.memory, 1, chunk.size, out);
340357
fprintf(out, "\r\n");
@@ -345,6 +362,7 @@ static void DownloadTLESource(CURL *curl, const char *url, FILE *out)
345362
}
346363

347364
free(chunk.memory);
365+
return ok;
348366
}
349367

350368
static void ReloadTLEsLocally(UIContext *ctx, AppConfig *cfg)
@@ -365,45 +383,88 @@ static void ReloadTLEsLocally(UIContext *ctx, AppConfig *cfg)
365383
LoadSatSelection();
366384
}
367385

368-
static void PullTLEData(UIContext *ctx, AppConfig *cfg)
386+
/* background thread: downloads all selected TLE sources to data.tle */
387+
static void *PullTLEThread(void *arg)
369388
{
389+
(void)arg;
390+
AppConfig *cfg = pull_cfg;
391+
370392
FILE *out = fopen("data.tle", "wb");
371-
if (out)
393+
if (!out)
394+
{
395+
pull_state = PULL_ERROR;
396+
return NULL;
397+
}
398+
399+
unsigned int mask = 0, ret_mask = 0, cust_mask = 0;
400+
for (int i = 0; i < 25; i++)
401+
if (celestrak_selected[i]) mask |= (1 << i);
402+
for (int i = 0; i < NUM_RETLECTOR_SOURCES; i++)
403+
if (retlector_selected[i]) ret_mask |= (1 << i);
404+
for (int i = 0; i < cfg->custom_tle_source_count; i++)
405+
if (cfg->custom_tle_sources[i].selected) cust_mask |= (1 << i);
406+
407+
fprintf(out, "# EPOCH:%ld MASK:%u CUST_MASK:%u RET_MASK:%u\r\n", (long)time(NULL), mask, cust_mask, ret_mask);
408+
409+
int ok_count = 0, fail_count = 0;
410+
CURL *curl = curl_easy_init();
411+
if (curl)
372412
{
373-
unsigned int mask = 0, ret_mask = 0, cust_mask = 0;
374-
for (int i = 0; i < 25; i++)
375-
if (celestrak_selected[i]) mask |= (1 << i);
376413
for (int i = 0; i < NUM_RETLECTOR_SOURCES; i++)
377-
if (retlector_selected[i]) ret_mask |= (1 << i);
414+
if (ret_mask & (1 << i))
415+
{ if (DownloadTLESource(curl, RETLECTOR_SOURCES[i].url, out)) ok_count++; else fail_count++; }
416+
417+
for (int i = 0; i < 25; i++)
418+
if (mask & (1 << i))
419+
{ if (DownloadTLESource(curl, SOURCES[i].url, out)) ok_count++; else fail_count++; }
420+
378421
for (int i = 0; i < cfg->custom_tle_source_count; i++)
379-
if (cfg->custom_tle_sources[i].selected) cust_mask |= (1 << i);
422+
if (cust_mask & (1 << i))
423+
{ if (DownloadTLESource(curl, cfg->custom_tle_sources[i].url, out)) ok_count++; else fail_count++; }
380424

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

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

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

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

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

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

463+
/* called each frame from DrawGUI to finish reload on the main thread */
464+
static void FinishPullIfDone(UIContext *ctx, AppConfig *cfg)
465+
{
466+
if (pull_state == PULL_DONE)
467+
{
407468
if (ctx)
408469
{
409470
*ctx->selected_sat = NULL;
@@ -424,6 +485,7 @@ static void PullTLEData(UIContext *ctx, AppConfig *cfg)
424485
}
425486
LoadSatSelection();
426487
data_tle_epoch = time(NULL);
488+
pull_state = PULL_IDLE;
427489
}
428490
}
429491

@@ -968,6 +1030,8 @@ static bool DrawMaterialWindow(Rectangle bounds, const char *title, AppConfig *c
9681030
/* main ui rendering loop */
9691031
void DrawGUI(UIContext *ctx, AppConfig *cfg, Font customFont)
9701032
{
1033+
FinishPullIfDone(ctx, cfg);
1034+
9711035
*ctx->show_scope = show_scope_dialog;
9721036
*ctx->scope_az = scope_az;
9731037
*ctx->scope_el = scope_el;
@@ -1599,9 +1663,17 @@ void DrawGUI(UIContext *ctx, AppConfig *cfg, Font customFont)
15991663
}
16001664
DrawUIText(customFont, age_str, tm_x + 10 * cfg->ui_scale, tm_y + 35 * cfg->ui_scale, 16 * cfg->ui_scale, cfg->text_main);
16011665

1602-
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"))
16031666
{
1604-
PullTLEData(ctx, cfg);
1667+
const char *btn_label = "Apply";
1668+
if (pull_state == PULL_BUSY) btn_label = "Pulling..";
1669+
else if (pull_state == PULL_ERROR) btn_label = "Error";
1670+
else if (pull_partial) btn_label = "Partial";
1671+
if (pull_state == PULL_BUSY) GuiDisable();
1672+
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))
1673+
{
1674+
PullTLEData(cfg);
1675+
}
1676+
if (pull_state == PULL_BUSY) GuiEnable();
16051677
}
16061678

16071679
float total_height = 28 * cfg->ui_scale + (retlector_expanded ? NUM_RETLECTOR_SOURCES * 25 * cfg->ui_scale : 0);
@@ -3205,10 +3277,13 @@ case WND_SCOPE:
32053277
float spacing = 15 * cfg->ui_scale;
32063278
float startX = tleWarnWindow.x + (tleWarnWindow.width - (3 * btnWidth + 2 * spacing)) / 2.0f;
32073279

3208-
if (GuiButton((Rectangle){startX, tleWarnWindow.y + 105 * cfg->ui_scale, btnWidth, 35 * cfg->ui_scale}, "#112# Update All")) {
3209-
PullTLEData(ctx, cfg);
3280+
if (pull_state == PULL_BUSY) GuiDisable();
3281+
if (GuiButton((Rectangle){startX, tleWarnWindow.y + 105 * cfg->ui_scale, btnWidth, 35 * cfg->ui_scale},
3282+
pull_state == PULL_BUSY ? "Pulling.." : "#112# Update All")) {
3283+
PullTLEData(cfg);
32103284
show_tle_warning = false;
32113285
}
3286+
if (pull_state == PULL_BUSY) GuiEnable();
32123287
if (GuiButton((Rectangle){startX + btnWidth + spacing, tleWarnWindow.y + 105 * cfg->ui_scale, btnWidth, 35 * cfg->ui_scale}, "#1# Manage")) {
32133288
show_tle_warning = false;
32143289
if (!show_tle_mgr_dialog) {

0 commit comments

Comments
 (0)