libbgi: idle-хук в ожидании кадра; им лечится потеря нажатий с Shift
Причина потерь (замеры — applications/PoP/roomtest/TASKS.md, KBD-1): при
зажатом Shift PS/2 обрамляет расширенный код «фиктивным шифтом», нажатие
стрелки становится 5 байтами вместо 2, а импульс запроса прерывания здесь
теряется примерно в 44 % случаев — трёхбайтовый FIFO SIO переполняется, и
байт пропадает ДО чтения порта. Лечится только плотным вычерпыванием: раз
в ~0.5 мс. Столько времени есть даром — при пейсинге «3 растровых кадра на
логический тик» процессор проводит ~42 мс из 60 в gfx_wait_vsync, крутя
опрос луча и больше ничего не делая.
- gfx_set_idle_hook(fn) — что вызывать, пока gfx_wait_vsync ждёт луч.
Состояние в отдельном data-модуле (_gfx_idle_state.c), чтобы не тянуть
сеттер в программы, которые хук не ставят.
- Лучевой цикл зовёт хук в обеих фазах. BC (счётчик таймаута)
сохраняется, косвенный вызов — push адреса возврата + jp (hl), так как
`call (hl)` в Z80 нет; без хука это ret по нулевому указателю, порядка
двух десятков тактов в цикле, который и так сжигает время.
- Путь FPS-делителя не затронут: там ожидание через HALT.
- roomtest вешает на хук kbd_raw_poll.
Проверка в MAME счётчиками (брейкпоинты с { b@ADDR = b@ADDR+1 ; g } на
чтении порта 0x18 и на установке make-бита): 35 нажатий Shift+Home → 35
make, ноль потерь; до фикса было 9 из 10. Боевой сценарий: четыре Shift+→
подряд дали четыре осторожных шага (Kid.x 114 -> 147). _CODE +170 Б,
кадровый бюджет не затронут.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,20 @@
|
||||
|
||||
## P0 — делаем сейчас
|
||||
|
||||
### KBD-1. Shift + стрелки: нажатия теряются — определить причину
|
||||
### KBD-1. Shift + стрелки: нажатия теряются — **ПРИЧИНА НАЙДЕНА, ФИКС ПРОВЕРЕН**
|
||||
|
||||
> **Итог (2026-08-01).** Причина — не наш код и не DI-окна графики: при
|
||||
> зажатом Shift PS/2 удваивает трафик («fake shift»), а импульс запроса
|
||||
> прерывания здесь теряется примерно в 44 % случаев, и трёхбайтовый FIFO
|
||||
> SIO переполняется. Лечится ПЛОТНЫМ опросом: `kbd_raw_poll` повешен
|
||||
> idle-хуком на ожидание кадра (`gfx_set_idle_hook`, новый API libbgi) —
|
||||
> процессор всё равно проводит там ~42 мс из 60, крутя опрос луча.
|
||||
>
|
||||
> **Проверка в roomtest тем же счётным методом: 35 нажатий Shift+Home →
|
||||
> 35 make, ноль потерь** (до фикса — 9 из 10). Боевой сценарий тоже:
|
||||
> четыре Shift+→ подряд дали четыре осторожных шага, `Kid.x` 114 → 147.
|
||||
> Цена: `_CODE` +170 Б, кадровый бюджет не затронут (опрос стоит в
|
||||
> простое). Ниже — полный протокол, как к этому пришли.
|
||||
|
||||
**Симптом (пользователь, 2026-08-01).** Залипаний почти нет, но при
|
||||
УДЕРЖИВАЕМОМ Shift первые один-два нажатия ← дают осторожные шаги, дальше
|
||||
@@ -183,8 +196,8 @@
|
||||
Плюс пришлось бы переписать все наши константы кодов под Set 3. Значит это
|
||||
кандидат на «когда дойдём до реального железа», а не на сейчас.
|
||||
|
||||
**Что делать (в порядке зависимостей):**
|
||||
1. Idle-хук в libbgi: `gfx_set_idle_hook(fn)`, вызывается в цикле ожидания
|
||||
**Что сделано по этому плану (2026-08-01):**
|
||||
1. ✅ Idle-хук в libbgi: `gfx_set_idle_hook(fn)`, вызывается в цикле ожидания
|
||||
луча внутри `gfx_wait_vsync`. Приложение ставит туда `kbd_raw_poll`.
|
||||
Полезен не только нам — любой программе даёт «качать» что-то в ожидании
|
||||
кадра. Осторожно с регистрами: цикл ждёт на BC-таймауте, вокруг вызова
|
||||
@@ -196,9 +209,14 @@
|
||||
потери случаются ТОЛЬКО при зажатом модификаторе (замерено; без Shift
|
||||
потерь нет). Значит хук можно взводить лишь пока нажат Shift/Ctrl/Alt —
|
||||
тогда опрос работает исключительно в той ситуации, ради которой заведён.
|
||||
2. Вернуть вызовы в занятую треть кадра (они бесплатны, просто сами по себе
|
||||
ничего не решали).
|
||||
3. Перемерить тем же счётным методом уже в roomtest: цель — 25/25.
|
||||
2. ✅ Перемерено в roomtest тем же счётным методом: **35/35**, потерь нет.
|
||||
3. ⏳ Вызовы в занятую треть кадра НЕ возвращены: одного idle-хука хватило.
|
||||
Держать в уме, если на реальном железе или на более тяжёлых сценах
|
||||
(несколько стражей) потери появятся снова — накрыть блиты дешевле, чем
|
||||
изобретать что-то новое.
|
||||
4. ⏳ Ручная проверка пользователем — без неё этап не закрыт: скриптовые
|
||||
нажатия ровнее человеческих, и «залипания до отпускания Shift» они не
|
||||
воспроизводили с самого начала.
|
||||
|
||||
**Куда смотреть дальше, если плотного опроса не хватит.**
|
||||
1. **Драйвер MAME — НЕ ТРОГАЕМ** (решение пользователя: пересборка MAME на
|
||||
|
||||
@@ -57,22 +57,32 @@ const uint8_t n_banks = 4;
|
||||
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
||||
uint8_t pop_cheats;
|
||||
|
||||
/* ПОТЕРЯ НАЖАТИЙ ПРИ ЗАЖАТОМ Shift — НЕ ЧИНИТСЯ ИЗ ЭТОГО ЦИКЛА.
|
||||
/* ПОТЕРЯ НАЖАТИЙ ПРИ ЗАЖАТОМ Shift — лечится ПЛОТНЫМ опросом клавиатуры.
|
||||
*
|
||||
* Здесь стояли шесть вызовов kbd_raw_poll() (вычерпать FIFO клавиатуры
|
||||
* после каждой тяжёлой фазы кадра). Убраны: замерено, что они не меняют
|
||||
* НИЧЕГО — 9 дошедших make-байт из 10 нажатий и с ними, и без них. Они
|
||||
* стояли ровно там, где прерывания и так разрешены, то есть добавляли то,
|
||||
* что трамплин делает сам.
|
||||
* При зажатом Shift PS/2 обрамляет расширенный код «фиктивным шифтом», и
|
||||
* одно нажатие стрелки превращается в 5 байт вместо 2 (отпускание — тоже
|
||||
* 5). Приёмный FIFO SIO — 3 байта, а импульс запроса прерывания здесь
|
||||
* теряется примерно в 44 % случаев, поэтому байты копятся и один из
|
||||
* полусотни пропадает ДО чтения порта: нажатие «не срабатывает».
|
||||
*
|
||||
* Настоящая причина лежит НИЖЕ нашего кода: при зажатом Shift PS/2 шлёт на
|
||||
* расширенную клавишу вдвое больше байт («fake shift»), а импульс запроса
|
||||
* прерывания пропускается примерно в 44 % случаев — байты копятся в
|
||||
* трёхбайтовом FIFO SIO и один из полусотни теряется ДО чтения порта.
|
||||
* Длина наших DI-окон ни при чём: в замороженном кадре, где блитов нет
|
||||
* вообще, потерь БОЛЬШЕ, а не меньше.
|
||||
* Что НЕ помогает (замерено, TASKS.md/KBD-1): редкий опрос (несколько
|
||||
* вызовов за кадр — они попадают туда, где прерывания и так разрешены, и
|
||||
* лишь дублируют трамплин) и укорачивание DI-окон графики (в кадре вообще
|
||||
* без блитов потерь БОЛЬШЕ). Снимать `di` в accel-ядрах нельзя — машина
|
||||
* уходит в перезагрузку.
|
||||
*
|
||||
* Полный протокол замеров и куда копать дальше — TASKS.md, KBD-1. */
|
||||
* Что помогает: опрос раз в ~0.5 мс. Столько времени берётся из ожидания
|
||||
* кадра — процессор всё равно крутит там ~42 мс из 60, ничего не делая.
|
||||
* Поэтому kbd_raw_poll вешается idle-хуком на gfx_wait_vsync (см.
|
||||
* kbd_idle ниже); полезной работы это не отнимает. Потолок приёма
|
||||
* подтверждён отдельно (tests/kbdpoll: 25 нажатий → 25, 250 байт из 250). */
|
||||
|
||||
/* Обёртка ради типа: хук — void(*)(void), а kbd_raw_poll возвращает
|
||||
* «было ли что вычерпано». Приводить указатель кастом не хочется. */
|
||||
static void kbd_idle(void)
|
||||
{
|
||||
kbd_raw_poll();
|
||||
}
|
||||
|
||||
/* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */
|
||||
#define SEQ_STAND 2
|
||||
@@ -317,6 +327,7 @@ int main(void)
|
||||
puts("kbd_raw open failed");
|
||||
return 1;
|
||||
}
|
||||
gfx_set_idle_hook(kbd_idle); /* вычерпывать FIFO, пока ждём кадр */
|
||||
|
||||
while (!kbd_raw_down(KBD_ESC)) {
|
||||
/* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один
|
||||
@@ -583,6 +594,7 @@ int main(void)
|
||||
}
|
||||
}
|
||||
|
||||
gfx_set_idle_hook(0); /* снять хук ДО закрытия raw-канала */
|
||||
pop_ctrl_close();
|
||||
closegraph();
|
||||
pop_bg_free();
|
||||
|
||||
Reference in New Issue
Block a user