mdview2: прогрессивная загрузка (первый экран сразу, скролл во время индексации)

Препроцессинг 52КБ занимает ~10с; теперь UI не ждёт его завершения.
index_lines() кооперативно вызывает progress_tick() (без прерываний —
IM2 отложен):
- первый экран рисуется, как только готово ≥ VIEW_H строк (~0.3с);
- статус-бар показывает растущее число обработанных строк с многоточием
  ("L 1-30 / 247...");
- ↑↓ PgUp PgDn Home End (=последняя готовая страница) и F1 работают по
  УЖЕ готовым строкам (drawable_lines = [0..n_lines-2], т.к. последний
  сегмент ещё в g_cells до flush);
- Esc/F10 во время загрузки — корректный выход (прерывание индексации
  флагом g_abort + штатный unload_file/pal_reset/clrscr).

После каждого тика форсируем ре-маппинг W3 (cur_page=0xFF), т.к.
отрисовка/WINREST/BIOS могли сбить страницу, на которую опирается fb().

Временно: debug-индикатор времени обработки "t=Ns" в зоне имени файла
(по просьбе — следить за временем при дальнейших оптимизациях).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 16:46:57 +03:00
parent de11882d16
commit 50596e6a2a
+116 -13
View File
@@ -30,6 +30,7 @@
#include <sprinter.h> #include <sprinter.h>
#include <sprinter_mem.h> #include <sprinter_mem.h>
#include <palette.h> #include <palette.h>
#include <time.h> /* DEBUG: время обработки в статус-баре прогресса */
/* ---- Геометрия экрана -------------------------------------------- */ /* ---- Геометрия экрана -------------------------------------------- */
@@ -282,6 +283,15 @@ static uint8_t cur_page = 0xFF; /* 0xFF = в W3 ещё не отображ
static uint8_t viewport_x = 0; /* горизонтальный сдвиг для nowrap-строк */ static uint8_t viewport_x = 0; /* горизонтальный сдвиг для nowrap-строк */
static char filename[64] = {0}; static char filename[64] = {0};
/* Прогрессивная загрузка: рисуем первый экран и пускаем скролл, пока
* index_lines() ещё досканирует файл (кооперативно, без прерываний). */
static uint8_t g_loading = 0; /* 1 во время index_lines() */
static uint8_t g_first_drawn = 0; /* первый экран уже нарисован */
static uint8_t g_abort = 0; /* F10/Esc во время загрузки → прервать индексацию */
static uint16_t g_last_prog = 0; /* n_lines на последнем обновлении статуса */
static datetime_t g_load_t0; /* DEBUG: старт обработки (для времени в статусе) */
static void progress_tick(void); /* определена ниже (нужна в index_lines) */
#define HPAN_STEP 8u #define HPAN_STEP 8u
/* Спиннер в статус-баре (col=8,row=0). /* Спиннер в статус-баре (col=8,row=0).
@@ -1291,10 +1301,11 @@ static void index_lines(void)
if (file_size == 0) return; if (file_size == 0) return;
uint32_t p = 0; uint32_t p = 0;
while (p < file_size && n_lines < max_lines) { while (p < file_size && n_lines < max_lines && !g_abort) {
if (g_loading)
if ((n_lines & 15) == 0) progress_tick(); /* первый экран + статус + скролл по готовым данным */
else if ((n_lines & 15) == 0)
spinner_tick(); spinner_tick();
/* Пустая строка — разделитель параграфов. /* Пустая строка — разделитель параграфов.
@@ -1562,14 +1573,23 @@ static void draw_viewport_from_cache(void)
} }
} }
/* Возвращает процент прокрутки (0..100) относительно доступного диапазона. */ /* Число строк, готовых к показу. Во время загрузки последняя эмитированная
static uint8_t calc_pct(void) * строка ещё в g_cells (не зафлашена), поэтому доступны [0..n_lines-2]. */
static uint16_t drawable_lines(void)
{ {
if (g_loading) return (n_lines >= 1) ? (uint16_t)(n_lines - 1) : 0;
return n_lines;
}
/* Возвращает процент прокрутки (0..100) относительно доступного диапазона. */
static uint8_t calc_pct(void)
{
uint16_t total = drawable_lines();
uint8_t pct; uint8_t pct;
if (n_lines <= VIEW_H) if (total <= VIEW_H)
pct = 100; pct = 100;
else else
pct = (uint8_t)((uint32_t)top_line * 100UL / (uint32_t)(n_lines - VIEW_H)); pct = (uint8_t)((uint32_t)top_line * 100UL / (uint32_t)(total - VIEW_H));
return pct; return pct;
} }
@@ -1577,8 +1597,9 @@ static uint8_t calc_pct(void)
* Полностью перезаписывает колонки 45..79, чтобы не оставлять «хвосты». */ * Полностью перезаписывает колонки 45..79, чтобы не оставлять «хвосты». */
static void render_status_numbers(void) static void render_status_numbers(void)
{ {
uint16_t last = top_line + VIEW_H; uint16_t total = drawable_lines();
if (last > n_lines) last = n_lines; uint16_t last = top_line + VIEW_H;
if (last > total) last = total;
uint8_t pct = calc_pct(); uint8_t pct = calc_pct();
textattr(ATTR_BAR); textattr(ATTR_BAR);
@@ -1592,13 +1613,25 @@ static void render_status_numbers(void)
cputs("-"); cputs("-");
dec16(last); dec16(last);
cputs(" / "); cputs(" / ");
dec16(n_lines); dec16(total);
if (g_loading) cputs("..."); /* ещё грузится */
wrchar(70, 0, 0xB3, ATTR_BAR); /* вертикальный разделитель */ wrchar(70, 0, 0xB3, ATTR_BAR); /* вертикальный разделитель */
gotoxy( 73, 0); gotoxy( 73, 0);
dec8(pct); dec8(pct);
cputs("%"); cputs("%");
if (g_loading) { /* DEBUG: время обработки (зона имени файла) */
datetime_t now;
getdatetime(&now);
int16_t el = (int16_t)now.minute*60 + now.second
- ((int16_t)g_load_t0.minute*60 + g_load_t0.second);
if (el < 0) el += 3600;
gotoxy(31, 0);
cputs("t=");
dec16((uint16_t)el);
cputs("s "); /* трейлинг-пробелы затирают прошлую ширину */
}
} }
/* Полностью перерисовывает статус-бар (заголовок + числа). */ /* Полностью перерисовывает статус-бар (заголовок + числа). */
@@ -1813,6 +1846,66 @@ static void show_help(void)
render_menu(); render_menu();
} }
/* Обработка клавиши во время прогрессивной загрузки: скролл строго в
* пределах готовых строк (drawable_lines); End = последняя готовая страница;
* F1 = справка (show_help сам блокирует и перерисовывает). */
static void load_key(uint16_t k)
{
uint8_t ascii = (uint8_t)(k & 0xFF);
uint8_t scan = (uint8_t)((k >> 8) & 0x7F);
uint16_t dr = drawable_lines();
uint16_t maxtop = (dr > VIEW_H) ? (uint16_t)(dr - VIEW_H) : 0;
uint16_t nt = top_line;
if (ascii == 0x1B || scan == KEY_F10) { g_abort = 1; return; } /* Esc/F10 → выход */
if (ascii) return; /* прочие ascii игнорируем */
switch (scan) {
case KEY_UP: nt = (top_line >= 1) ? (uint16_t)(top_line - 1) : 0; break;
case KEY_DOWN: nt = top_line + 1; break;
case KEY_PGUP: nt = (top_line >= VIEW_H) ? (uint16_t)(top_line - VIEW_H) : 0; break;
case KEY_PGDN: nt = top_line + VIEW_H; break;
case KEY_HOME: top_line = 0; viewport_x = 0; draw_viewport_from_cache(); render_status_numbers(); return;
case KEY_END: top_line = maxtop; viewport_x = 0; draw_viewport_from_cache(); render_status_numbers(); return;
case KEY_LEFT: scroll_h(-(int8_t)HPAN_STEP); render_status_numbers(); return;
case KEY_RIGHT: scroll_h(+(int8_t)HPAN_STEP); render_status_numbers(); return;
case KEY_F1: show_help(); return;
default: return;
}
if (nt > maxtop) nt = maxtop;
if (nt != top_line) {
top_line = nt;
draw_viewport_from_cache();
render_status_numbers();
}
}
/* Кооперативный «тик» прогрессивной загрузки — вызывается из index_lines(). */
static void progress_tick(void)
{
uint8_t touched = 0;
if (!g_first_drawn && drawable_lines() >= VIEW_H) {
draw_viewport_from_cache();
render_status_numbers();
g_first_drawn = 1;
touched = 1;
}
if (n_lines >= (uint16_t)(g_last_prog + 20)) {
g_last_prog = n_lines;
spinner_tick();
if (g_first_drawn) render_status_numbers();
touched = 1;
}
if (g_first_drawn && kbhit()) {
load_key(getkey());
touched = 1;
}
/* Отрисовка/WINREST/BIOS могли сбить маппинг W3, на который опирается fb()
* при индексации → форсируем ре-маппинг страницы файла на следующем fb(). */
if (touched) cur_page = 0xFF;
}
/* ================================================================== /* ==================================================================
* Точка входа * Точка входа
* ================================================================== */ * ================================================================== */
@@ -1876,16 +1969,26 @@ int main(int argc, char **argv)
cputs("\n\r\n\r"); cputs("\n\r\n\r");
return 1; return 1;
} }
/* Прогрессивная загрузка: index_lines() сама рисует первый экран и пускает
* скролл по готовым данным через progress_tick(). */
g_loading = 1;
g_first_drawn = 0;
g_last_prog = 0;
getdatetime(&g_load_t0); /* DEBUG: старт обработки */
index_lines(); index_lines();
g_loading = 0;
spinner_show(0); spinner_show(0);
if (g_abort) goto exit_loop; /* F10/Esc во время загрузки — корректный выход */
if (n_lines == 0) { if (n_lines == 0) {
die("mdview: empty file"); die("mdview: empty file");
unload_file(); unload_file();
return 1; return 1;
} }
/* Финальная отрисовка: все строки готовы, top_line мог уехать при скролле. */
clamp_top();
render_full_status(); render_full_status();
draw_viewport_from_cache(); /* Фаза 4: рендер уже сделан во время index_lines(), рисуем только из кэша */ draw_viewport_from_cache();
for (;;) { for (;;) {
uint16_t k = getkey(); uint16_t k = getkey();