Архивы ресурсов PBA1: звук одним файлом, музыка потоком; ALLOCS 6000

ЗАМЕР, из которого всё выросло (MAME, 2026-08-25): `open` стоит 51,4 мс,
а чтение 16 КБ из уже открытого файла — 32,6 мс.  То есть 61 % времени
загрузки уходит на ОТКРЫТИЕ, а не на данные, и чистая скорость чтения —
491 КБ/с, а не 260.  На диске игры 343 файла; одна только стартовая пачка
(kid+shadow+bg+sound+guard, ~142 файла) — это 7,3 секунды чистого open.

* toolchain/pop_pack_arc.py + pop_arc.c/h (банк 8) — формат PBA1:
  заголовок 512 Б, магия, count, записи по 4 байта (offset в СЕКТОРАХ —
  архив бывает больше 64 КБ; size в байтах — ресурс всегда <= 16 КБ,
  он обязан лезть в EMM-страницу).  Таблицу держит вызывающий на своём
  стеке: в W2 её класть нельзя, а стека в точке загрузки израсходовано
  75 байт из 1279 (замер там же).
* libc: bank_read_page — чтение из УЖЕ ОТКРЫТОГО файла в EMM-страницу.
  bank_load_file переписана через неё, поэтому дублирования работы с W3
  не осталось и резидент почти не вырос.
* Набор эффектов: 10 файлов -> SND\snd.arc, один open вместо десяти.
* Музыка: трек лежит ОДНИМ файлом и читается порциями по странице —
  нарезка на куски больше не нужна.  Этот же путь понадобится финалу
  `won` (1,2 МБ), который в память целиком не влезает.
* Менеджер звука приведён к оригиналу: играющая музыка участвует в
  таблицах приоритета наравне с эффектами (seg000:1672 + data.h:433).
  Почти вся музыка НЕперебиваема, поэтому поверх неё эффект не стартует —
  ровно как в SDLPoP; раньше эффект её перебивал.
* ALLOCS 3000 -> 6000 (решение пользователя): сборка +20 с, резидент
  -161 байт.  Куча 87 -> 248 Б.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 17:13:25 +03:00
parent 34ba5f71d5
commit ba3aca07bf
25 changed files with 357 additions and 67 deletions
+6
View File
@@ -116,6 +116,12 @@ void bank_write(uint8_t phys_page, uint16_t off, const void *src, uint16_t n)
* Возвращает фактический размер либо -1 + errno. */
int bank_load_file(uint8_t phys_page, uint16_t off, const char *path,
uint16_t max_size);
/* То же, но из УЖЕ ОТКРЫТОГО файла и с текущей позиции (её ставит вызывающий
* через lseek). Нужна тем, кто читает из одного файла много кусков: `open`
* стоит дороже самого чтения (замер: 51,4 мс против 32,6 мс на 16 КБ), и
* держать fd открытым — единственный способ эту цену не платить. */
int bank_read_page(int fd, uint8_t phys_page, uint16_t off, uint16_t size);
int bank_save_file(uint8_t phys_page, uint16_t off, const char *path,
uint16_t size);
+11 -15
View File
@@ -1,33 +1,29 @@
/*
* bank_load_file — прочитать файл непосредственно в физическую EMM-страницу.
* bank_load_file — прочитать файл целиком в физическую EMM-страницу.
*
* ESTEX принимает файловый буфер только в W1..W3; используем W3, сохраняя и
* восстанавливая его раскладку. Функция резидентна, поэтому её безопасно
* вызывать из банкового кода huge: после open() указатель path больше не
* читается, пока W3 временно занят целевой страницей.
* Тонкая обёртка над bank_read_page: открыть, перенести, закрыть. Вся
* работа с W3 (ESTEX принимает файловый буфер только в W1..W3) живёт там,
* в одном месте — дублировать её здесь незачем, а резидент W1 у
* приложений тесный.
*
* Кто читает из одного файла НЕСКОЛЬКО кусков (архив ресурсов, поток
* музыки), обязан звать bank_read_page напрямую, держа fd открытым:
* `open` дороже самого чтения — 51,4 мс против 32,6 мс на 16 КБ (замер в
* MAME, PoP, 2026-08-25).
*
* Возврат: фактически прочитанный размер либо -1 + errno.
*/
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sprinter.h>
#include <sprinter_mem.h>
int bank_load_file(uint8_t phys_page, uint16_t off, const char *path,
uint16_t max_size)
{
uint8_t saved;
int fd, n;
if (off > 0x3FFFu || max_size > (uint16_t)(0x4000u - off)) {
errno=EINVAL; return -1;
}
fd = open(path, O_RDONLY);
if (fd < 0) return -1;
saved = _io_page_w3;
sprinter_page_w3(phys_page);
n = read(fd, (void *)(0xC000u + off), max_size);
sprinter_page_w3(saved);
n = bank_read_page(fd, phys_page, off, max_size);
close(fd);
return n;
}
+35
View File
@@ -0,0 +1,35 @@
/*
* bank_read_page — прочитать из УЖЕ ОТКРЫТОГО файла прямо в EMM-страницу.
*
* Зачем отдельно от bank_load_file: та открывает и закрывает файл на каждый
* вызов, а `open` на Sprinter стоит дорого — замер в MAME (PoP, 2026-08-25):
* open 51,4 мс против 32,6 мс на чтение 16 КБ, то есть 61 % времени уходит
* на открытие. Кто читает из одного файла несколько кусков (архив ресурсов,
* поток музыки), обязан держать fd открытым — тогда эта цена платится раз.
*
* ESTEX принимает файловый буфер только в W1..W3; используем W3, сохраняя и
* восстанавливая раскладку. Функция резидентна, поэтому её безопасно
* вызывать из банкового кода huge: пока W3 занят целевой страницей, никакой
* памяти вызывающего мы не читаем. Позицию в файле вызывающий ставит сам
* (lseek) — здесь только перенос.
*
* Возврат: фактически прочитанный размер либо -1 + errno.
*/
#include <unistd.h>
#include <errno.h>
#include <sprinter.h>
#include <sprinter_mem.h>
int bank_read_page(int fd, uint8_t phys_page, uint16_t off, uint16_t size)
{
uint8_t saved;
int n;
if (off > 0x3FFFu || size > (uint16_t)(0x4000u - off)) {
errno = EINVAL; return -1;
}
saved = _io_page_w3;
sprinter_page_w3(phys_page);
n = read(fd, (void *)(0xC000u + off), size);
sprinter_page_w3(saved);
return n;
}