From bc3483c2ddcbe3bb9df042d59c8b20c02ff54460 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Tue, 30 Jun 2026 20:48:35 +0300 Subject: [PATCH] =?UTF-8?q?mdview2:=20=D0=BB=D0=B8=D0=BC=D0=B8=D1=82=20?= =?UTF-8?q?=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=20256=20=D0=9A=D0=91,=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D1=8B=D0=B2=20=D1=81=20=D1=81=D0=BE=D0=BE=D0=B1?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=B2=D0=BC=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=BE=20=D0=BF=D0=BE=D1=82=D0=B5=D1=80=D0=B8=20=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MAX_PAGES 8->16 (256 КБ), MAX_INDEX_PAGES/MAX_CACHE_DIR_PAGES 8->9 (18432 строк), кап контент-кэша 64->40 стр./набор (80 на оба). Бюджет worst-case (UTF-8 Latin+BOM): ~150 из 215 EMM-страниц. - При исчерпании контент-кэша (cache_reserve==0) или лимита строк индексация обрывается и последняя строка заменяется предупреждением (IF_TRUNC_MSG, рисуется вживую с ATTR_WARN — жёлтый по красному), без хвоста пустых строк. Раньше переполнение молча давало len=0 (пустые строки) без какого-либо индикатора. - help: лимиты обновлены (256 КБ / 18432 строк). Co-Authored-By: Claude Opus 4.8 --- examples/mdview2/mdview2.c | 6 +++--- examples/mdview2/mdview2.h | 9 ++++++++- examples/mdview2/mdview2_help.c | 2 +- examples/mdview2/mdview2_index.c | 32 ++++++++++++++++++++++++++++++-- examples/mdview2/mdview2_md.c | 13 +++++++++++++ 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/examples/mdview2/mdview2.c b/examples/mdview2/mdview2.c index 58d45a3..168b303 100644 --- a/examples/mdview2/mdview2.c +++ b/examples/mdview2/mdview2.c @@ -70,7 +70,7 @@ static const uint8_t md_pallete[16 * 4] = { * неинициализированные static-объекты могут конфликтовать по размещению * и перезаписывать друг друга (см. memory/sdcc_static_storage_gotcha). */ -#define MAX_INDEX_PAGES 8u /* максимум 8 * 2048 = 16384 сегментов индекса */ +#define MAX_INDEX_PAGES 9u /* максимум 9 * 2048 = 18432 сегментов индекса (256 КБ) */ /* ================================================================== * Рендер-кэш: директория и пул EMM-страниц @@ -97,9 +97,9 @@ static const uint8_t md_pallete[16 * 4] = { #define CACHE_DIR_REC_SIZE ((uint16_t)sizeof(cache_rec_t)) #define CACHE_DIR_RECS_PER_PAGE (16384 / CACHE_DIR_REC_SIZE) /* 2048 */ -#define MAX_CACHE_DIR_PAGES 8u +#define MAX_CACHE_DIR_PAGES 9u /* симметрично индексу: 9 * 2048 = 18432 записей */ /* MAX_CACHE_LINE_LEN — в mdview2.h (буфер ячеек g_cells живёт в mdview2_index.c). */ -#define MAX_CACHE_CONTENT_PAGES 64u /* запас: до 1 МБ кэша контента */ +#define MAX_CACHE_CONTENT_PAGES 40u /* кап контента на набор: 40 * 16 КБ = 640 КБ (80 на оба набора) */ static uint8_t cache_dir_blk = 0; static uint8_t cache_dir_pages = 0; diff --git a/examples/mdview2/mdview2.h b/examples/mdview2/mdview2.h index bb65a45..ac335a5 100644 --- a/examples/mdview2/mdview2.h +++ b/examples/mdview2/mdview2.h @@ -32,7 +32,7 @@ #define PAGE_BITS 14u #define PAGE_SIZE (1u << PAGE_BITS) /* размер EMM-страницы: 16 КБ */ #define PAGE_MASK ((uint16_t)(PAGE_SIZE - 1u)) -#define MAX_PAGES 8 /* 8 страниц × 16 КБ = 128 КБ */ +#define MAX_PAGES 16 /* 16 страниц × 16 КБ = 256 КБ */ #define MAX_FILE ((uint32_t)MAX_PAGES * PAGE_SIZE) #define FILE_BUF ((char *)0xC000) /* окно W3, куда мапится текущая EMM-страница */ @@ -57,6 +57,7 @@ #define ATTR_BAR_SPINNER COLOR(COLOR_WHITE, COLOR_LIGHTCYAN) #define ATTR_MENU_T COLOR(COLOR_BLACK, COLOR_LIGHTCYAN) #define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLACK) +#define ATTR_WARN COLOR(COLOR_YELLOW, COLOR_RED) /* строка-обрыв (исчерпан кэш/лимит) */ /* ---- Атрибуты диалога справки ------------------------------------ */ #define ATTR_HELP_BG COLOR(COLOR_LIGHTGRAY, COLOR_BLACK) @@ -87,6 +88,12 @@ #define IF_HSCROLL 0x10u /* блок горизонтально скроллируется ЦЕЛИКОМ (код/таблица), * включая строки короче 80 — двигаются как единый блок. * НЕ ставится на HR и границы fence (им нечего прятать). */ +#define IF_TRUNC_MSG 0x20u /* строка-обрыв: рисуется ВЖИВУЮ фикс. текстом (без контент-кэша); + * cache_rec.reserved несёт код причины (TRUNC_*). */ + +/* ---- Причина обрыва индексации (cache_rec.reserved при IF_TRUNC_MSG) ---- */ +#define TRUNC_CONTENT 1 /* исчерпан контент-кэш (MAX_CACHE_CONTENT_PAGES) */ +#define TRUNC_LINES 2 /* исчерпана ёмкость индекса (max_lines) */ /* ---- Словарь сегментов/стилей (общий для индексатора и таблиц) ---- */ /* Тип сегмента-продолжения, передаваемый в emit_seg()/inline_scan(). */ diff --git a/examples/mdview2/mdview2_help.c b/examples/mdview2/mdview2_help.c index fbf376d..5ed1ab2 100644 --- a/examples/mdview2/mdview2_help.c +++ b/examples/mdview2/mdview2_help.c @@ -95,7 +95,7 @@ void show_help(void) help_line(r++, " F8 Cycle CP866 / CP1251 / KOI8-R / UTF-8", ATTR_HELP_BG); help_line(r++, " Auto-detected on open; F8 to override", ATTR_HELP_BG); help_line(r++, "", ATTR_HELP_BG); - help_line(r++, " File size: up to 128 KB (EMM). Lines: up to 16384.", ATTR_HELP_BG); + help_line(r++, " File size: up to 256 KB (EMM). Lines: up to 18432.", ATTR_HELP_BG); help_line(r++, "", ATTR_HELP_BG); (void)getkey(); diff --git a/examples/mdview2/mdview2_index.c b/examples/mdview2/mdview2_index.c index 3bc2f8e..cc551a0 100644 --- a/examples/mdview2/mdview2_index.c +++ b/examples/mdview2/mdview2_index.c @@ -241,6 +241,12 @@ uint8_t g_ncells = 0; /* ячеек в текущем сег static uint8_t g_ncells_at_space = 0; /* снимок g_ncells на последнем пробеле (для усечения при переносе) */ static uint8_t g_skip_flush = 0; /* следующий emit_seg пропустит lag-флаш (pending уже зафлашен вручную, напр. перед таблицей) */ +/* Обрыв индексации: причина (0 / TRUNC_CONTENT / TRUNC_LINES) и строка, на + * которой упёрлись. Выставляются ОДИН раз (первая причина побеждает); по ним + * index_lines() в конце пишет строку-сообщение (IF_TRUNC_MSG, рисуется вживую). */ +static uint8_t g_trunc_cause = 0; +static uint16_t g_trunc_line = 0; + /* Пишет одну (char,attr)-ячейку в g_cells, молча игнорируя переполнение * MAX_CACHE_LINE_LEN. Оставлена ФУНКЦИЕЙ намеренно: инлайн в сильно * регистро-нагруженный скан-цикл на Z80/SDCC увеличивает спиллы и замедляет @@ -274,6 +280,9 @@ static void seg_commit_cells(uint16_t line_idx) if (cache_reserve((uint16_t)(g_ncells * 2u), &out_rec.page, &out_rec.off)) { cache_commit(out_rec.page, out_rec.off, g_cells, (uint16_t)(g_ncells * 2u)); } else { + /* Контент-кэш исчерпан: фиксируем причину и строку обрыва (один раз). + * index_lines() оборвёт цикл и перезапишет эту строку сообщением. */ + if (!g_trunc_cause) { g_trunc_cause = TRUNC_CONTENT; g_trunc_line = line_idx; } out_rec.page = 0; out_rec.off = 0; out_rec.len = 0; } cache_dir_put(line_idx, &out_rec); @@ -283,7 +292,11 @@ static void seg_commit_cells(uint16_t line_idx) static void emit_seg(uint32_t off, uint8_t style, uint8_t ckind, uint8_t cont) { (void)ckind; /* тип сегмента восстанавливается флагами idx-записи */ - if (n_lines >= max_lines) { index_truncated = 1; return; } + if (n_lines >= max_lines) { + index_truncated = 1; + if (!g_trunc_cause) { g_trunc_cause = TRUNC_LINES; g_trunc_line = (uint16_t)(max_lines - 1); } + return; + } cur_rec.off = off; cur_rec.flags = (uint8_t)(cont ? IF_CONT : 0); cur_rec.style = (uint8_t)(style & 7u); @@ -687,11 +700,13 @@ void index_lines(void) uint8_t in_block = 0; n_lines = 0; index_truncated = 0; + g_trunc_cause = 0; + g_trunc_line = 0; cur_seg_off = 0xFFFFFFFFu; /* пока не эмитили ни одного сегмента */ if (file_size == 0) return; uint32_t p = 0; - while (p < file_size && n_lines < max_lines && !g_abort) { + while (p < file_size && n_lines < max_lines && !g_abort && !g_trunc_cause) { /* UTF-8: держим конвертацию впереди позиции чтения (включая look-ahead * склейки/классификации ~ один абзац). file_size при этом = доступная @@ -957,6 +972,19 @@ void index_lines(void) * комментарий) — последняя эмитированная строка файла так и остаётся * незафлашенной, её ячейки ещё в g_cells; коммитим здесь. */ if (n_lines > 0) seg_commit_cells((uint16_t)(n_lines - 1)); + + /* Обрыв по контенту/лимиту строк (не путать с пользовательским g_abort): + * документ заканчиваем строкой-сообщением на g_trunc_line. Сообщение + * рисуется вживую (IF_TRUNC_MSG) — место в контент-кэше ему не нужно, что + * и снимает тупик «кэш полон, а сообщение тоже надо закэшировать». */ + if (g_trunc_cause && !g_abort) { + cache_rec_t r; + r.page = 0; r.off = 0; r.len = 0; + r.flags = IF_TRUNC_MSG; r.reserved = g_trunc_cause; + r.pad[0] = 0; r.pad[1] = 0; + cache_dir_put(g_trunc_line, &r); + n_lines = (uint16_t)(g_trunc_line + 1); /* документ кончается на строке-обрыве */ + } } #define EM_NONE 0 #define EM_BOLD 1 diff --git a/examples/mdview2/mdview2_md.c b/examples/mdview2/mdview2_md.c index 3e17751..697da83 100644 --- a/examples/mdview2/mdview2_md.c +++ b/examples/mdview2/mdview2_md.c @@ -28,6 +28,19 @@ static void draw_line_from_cache(uint16_t line_idx, uint8_t row) cache_rec_t rec; cache_dir_get(line_idx, &rec); + /* Строка-обрыв (исчерпан контент-кэш / лимит строк): фикс. текст вживую, + * без обращения к контент-кэшу (см. index_lines финализацию). */ + if (rec.flags & IF_TRUNC_MSG) { + bios_set_place(row, 0); + bios_fillcharattr(' ', ATTR_WARN, SCREEN_W); + put_str_attr(2, row, + (rec.reserved == TRUNC_LINES) + ? "*** Line limit reached - document truncated ***" + : "*** Content cache exhausted - document truncated ***", + ATTR_WARN); + return; + } + bios_set_place(row, 0); bios_fillcharattr(' ', ATTR_TEXT, SCREEN_W);