Volkov: добавить Sprinter Commander
Реализовать двухпанельный Commander от платформенного PoC до этапов P6-P20: EMM-каталог, сортировку и выбор, операции с файлами и деревьями, транзакционное копирование, метаданные, политику конфликтов и предварительную проверку свободного места. Добавить проектную документацию, HDD/MAME-сценарии и проверенные артефакты. Расширить libc операцией bank_write_page, исправлением режима O_RDONLY и связанными регрессионными проверками.
This commit is contained in:
@@ -0,0 +1,678 @@
|
||||
# Sprinter Commander: архитектура
|
||||
|
||||
Статус: P1/P2 подтверждены; одностраничная модель панели P3 реализована,
|
||||
проходит граничные проверки DSS
|
||||
|
||||
Связанные документы:
|
||||
[требования](commander-requirements.md),
|
||||
[клавиши и команды](commander-keymap.md),
|
||||
[план разработки](commander-roadmap.md),
|
||||
[платформенные пробы P1](p1-platform-probes.md),
|
||||
[экранный backend P2](p2-screen-backend.md),
|
||||
[результаты P2.1](p2-screen-probe-results.md),
|
||||
[результаты skeleton P2](p2-skeleton-results.md)
|
||||
|
||||
## 1. Цели архитектуры
|
||||
|
||||
Архитектура должна позволить получить небольшой технический PoC, не создавая
|
||||
тупиков для полной версии. Главные ограничения задаются не исходным VC, а
|
||||
платформой Sprinter:
|
||||
|
||||
- Z80 имеет 64-КБ адресное пространство;
|
||||
- W0 занят DSS/BIOS;
|
||||
- стек обязан находиться в W2;
|
||||
- полный список двух панелей не помещается вместе с кодом в near-память;
|
||||
- код полной версии потребует банков;
|
||||
- DSS имеет критический предел в восемь открытых файлов;
|
||||
- текстовый экран удобно восстанавливать непосредственно из EMM-страницы.
|
||||
|
||||
## 2. Принятые архитектурные решения
|
||||
|
||||
### ADR-001. Новая реализация, а не перенос
|
||||
|
||||
Commander пишется на C для SDCC. VC 4.05/4.99 используются как описание
|
||||
поведения и состава функций, Dos Navigator — как источник архитектурных
|
||||
идей. Зависимости от DOS interrupt API, сегментной модели x86, PSP, DTA,
|
||||
TSR, overlays, XMS и EMS в код проекта не переносятся.
|
||||
|
||||
### ADR-002. Лёгкий цикл событий вместо оконного framework
|
||||
|
||||
Приложение имеет один цикл событий, набор символьных команд и явное состояние
|
||||
экрана. Иерархия объектов наподобие Turbo Vision не используется.
|
||||
|
||||
Причины:
|
||||
|
||||
- меньше резидентного кода и данных;
|
||||
- нет виртуальных методов и потоковой сериализации объектов;
|
||||
- проще анализировать банковские вызовы;
|
||||
- легче гарантировать освобождение ресурсов.
|
||||
|
||||
### ADR-003. Целевой режим памяти — `big`
|
||||
|
||||
PoC собирается с `--memory big --safe`:
|
||||
|
||||
- W0 `0x0000–0x3FFF` — DSS/BIOS;
|
||||
- W1 `0x4000–0x7FFF` — переключаемые банки кода;
|
||||
- W2 `0x8000–0xBFFF` — HOME, DATA/BSS, heap и стек;
|
||||
- W3 `0xC000–0xFFFF` — EMM-страницы данных.
|
||||
|
||||
Если первоначальный skeleton временно собирается без банков, это не меняет
|
||||
контрактов модулей. До принятия PoC должна быть проверена именно сборка `big`.
|
||||
|
||||
### ADR-004. Панели, экран и рабочая область копирования хранятся в EMM
|
||||
|
||||
Начиная с P4 PoC выделяет один блок из четырёх последовательных 16-КБ страниц.
|
||||
P2/P3 использовали первые три страницы; четвёртая заранее резервируется текущим
|
||||
`ScApp`, чтобы копирование не увеличивало W2 и не меняло схему памяти во время
|
||||
операции:
|
||||
|
||||
| Логическая страница | Назначение | Использование |
|
||||
|---:|---|---|
|
||||
| 0 | записи левой панели | до 640 `ScDirEntry`, 1024 байта резерва |
|
||||
| 1 | записи правой панели | до 640 `ScDirEntry`, 1024 байта резерва |
|
||||
| 2 | экранный буфер | первые 5120 байт |
|
||||
| 3 | рабочая область copy job | первые 4096 байт в PoC |
|
||||
|
||||
Допускается выделение четырёх отдельных блоков, если эксперимент покажет
|
||||
проблему с последовательным блоком, но внешний интерфейс хранилища от этого
|
||||
не меняется.
|
||||
|
||||
### ADR-005. Запрещены долгоживущие указатели на W3
|
||||
|
||||
Указатель в диапазон `0xC000–0xFFFF` действителен только внутри функции,
|
||||
которая явно владеет отображением W3. После выхода из неё указатель считается
|
||||
недействительным.
|
||||
|
||||
Ни `PanelState`, ни `ScApp`, ни длительная операция не содержат near-указатель
|
||||
на содержимое EMM. Они хранят физическую страницу и смещение.
|
||||
|
||||
### ADR-006. Экран строится как модель ячеек
|
||||
|
||||
Каждая экранная ячейка представлена двумя байтами `(символ, атрибут)`. Формат
|
||||
совпадает с подтверждённым форматом ESTEX `WINREST`:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
uint8_t ch;
|
||||
uint8_t attr;
|
||||
} ScCell;
|
||||
```
|
||||
|
||||
Полный экран занимает `80 * 32 * 2 = 5120` байт. Renderer изменяет EMM-буфер,
|
||||
помечает строки грязными, а backend выводит либо диапазон строк, либо весь
|
||||
экран. Backend использует координатные ESTEX/BIOS-функции; основной пакетный
|
||||
путь — проверенный `WINREST`. Прямой доступ к VRAM не используется.
|
||||
|
||||
Renderer использует смысловые роли атрибутов (`frame`, `title`, `active`,
|
||||
`passive`, `error`), а не числа палитры. Модуль `sc_theme`, подтверждённый
|
||||
пробой P2.2, назначает ролям разные атрибуты, сохраняет затрагиваемые записи
|
||||
четырёх планов и восстанавливает их при cleanup. Это не меняет формат
|
||||
`ScCell` или API renderer.
|
||||
Системные BIOS/ESTEX-вызовы палитры разрешены; запрещён только обход системного
|
||||
экранного API через прямую адресацию VRAM и оконные дескрипторы BIOS.
|
||||
|
||||
Дескрипторы BIOS `WIN_OPEN/WIN_CLOSE/WIN_*_WIN` не используются: BIOS хранит
|
||||
только один описатель окна 0. Слово `WIN` в ESTEX `WINREST` означает область
|
||||
экрана и не делает эту функцию частью BIOS window manager.
|
||||
|
||||
### ADR-007. Источник файлов отделён от панели
|
||||
|
||||
Панель не вызывает `ffirst/fnext` напрямую. Она работает через модуль
|
||||
источника. В PoC реализован только `SC_SOURCE_REAL_FS`.
|
||||
|
||||
Вместо таблицы виртуальных функций используется `kind` и обычный диспетчер:
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
SC_SOURCE_REAL_FS = 0,
|
||||
SC_SOURCE_SEARCH,
|
||||
SC_SOURCE_ARCHIVE
|
||||
} ScSourceKind;
|
||||
```
|
||||
|
||||
Такой подход не создаёт near/far function pointers и упрощает размещение
|
||||
реализаций по банкам. `SEARCH` и `ARCHIVE` до соответствующих этапов должны
|
||||
возвращать внутреннюю ошибку `SC_ERR_UNSUPPORTED`, которая на границе
|
||||
платформы при необходимости отображается как ESTEX `EUNKOP`.
|
||||
|
||||
### ADR-008. Длительные операции являются jobs
|
||||
|
||||
Копирование, а позднее удаление, поиск и подсчёт размеров, оформляются как
|
||||
конечные автоматы:
|
||||
|
||||
```text
|
||||
INIT -> PREPARE -> RUN_CHUNK -> PRE_COMMIT -> COMMIT -> DONE
|
||||
| | |
|
||||
+-> CANCEL ----+-----------+
|
||||
+-> ERROR -----+-----------+
|
||||
```
|
||||
|
||||
Один шаг не должен надолго захватывать управление. Между блоками приложение
|
||||
может обновить прогресс и проверить отмену. Даже последний успешно записанный
|
||||
блок завершает только `RUN_CHUNK`: отдельный вызов переводит job через
|
||||
`PRE_COMMIT` к атомарному rename. Поэтому `Esc`, полученный после отображения
|
||||
`N/N`, всё ещё успевает закрыть и удалить temp до появления целевого файла.
|
||||
|
||||
### ADR-009. Viewer, editor и setup сначала отдельные программы
|
||||
|
||||
Основной Commander отвечает за панели и операции с файлами. Просмотрщик,
|
||||
редактор и настройка первоначально запускаются через `EXEC` как отдельные
|
||||
`.EXE`. Это уменьшает HOME, число банков и последствия ошибки компонента.
|
||||
|
||||
После 1.0 они могут быть интегрированы банками, если измерения покажут
|
||||
реальную пользу.
|
||||
|
||||
## 3. Слои системы
|
||||
|
||||
```text
|
||||
+------------------------------------------------------+
|
||||
| app: main loop, mode, active panel, command dispatch |
|
||||
+----------------------+-------------------------------+
|
||||
| panels/model | UI/dialogs/status |
|
||||
+----------------------+-------------------------------+
|
||||
| sources | renderer/screen buffer |
|
||||
+----------------------+-------------------------------+
|
||||
| jobs: copy/... | storage: EMM/page access |
|
||||
+----------------------+-------------------------------+
|
||||
| platform: ESTEX, keyboard, exec, screen, filesystem |
|
||||
+------------------------------------------------------+
|
||||
| libc + Sprinter DSS/BIOS |
|
||||
+------------------------------------------------------+
|
||||
```
|
||||
|
||||
Зависимости направлены вниз. `platform` не знает о панелях; `storage` не
|
||||
показывает сообщения; renderer не читает файловую систему.
|
||||
|
||||
## 4. Предлагаемая структура исходников
|
||||
|
||||
```text
|
||||
Makefile
|
||||
src/
|
||||
sprcmd.c точка входа и аварийная очистка
|
||||
sc_app.c главный цикл и состояние приложения
|
||||
sc_command.c преобразование событий в команды и dispatch
|
||||
sc_input.c клавиатурные события
|
||||
sc_screen.c EMM framebuffer и dirty rows
|
||||
sc_video_system.c координатный вывод ESTEX/BIOS без оконных ID
|
||||
sc_draw.c рамки, строки, числа, сокращение путей
|
||||
sc_panel.c состояние и поведение панели
|
||||
sc_panel_draw.c представление панели
|
||||
sc_dir_store.c записи каталога в EMM
|
||||
sc_sort.c сравнение и сортировка записей
|
||||
sc_source.c диспетчер источников
|
||||
sc_source_fs.c настоящий каталог DSS
|
||||
sc_copy_job.c PoC-копирование одного файла
|
||||
sc_copy_ui.c банковая оркестрация F5 и прогресс
|
||||
sc_dialog.c сообщения и подтверждения
|
||||
sc_path.c безопасная работа с путями 8.3
|
||||
sc_error.c нормализация errno/ESTEX ошибок
|
||||
sc_platform_exec.c ESTEX EXEC/WAIT
|
||||
sc_platform_screen.c wrappers WINREST/WRCHAR/SCROLL и screen mode
|
||||
include/
|
||||
sc_app.h
|
||||
sc_command.h
|
||||
sc_glyphs.h raw-коды CP866 для рамок и стрелок
|
||||
sc_input.h
|
||||
sc_screen.h
|
||||
sc_panel.h
|
||||
sc_store.h
|
||||
sc_source.h
|
||||
sc_job.h
|
||||
sc_path.h
|
||||
sc_platform.h
|
||||
banks/
|
||||
bank1_panel.c чтение/сортировка/обновление панелей
|
||||
bank2_fileops.c копирование и диалоги операции
|
||||
tests/
|
||||
... target-тесты и данные для MAME
|
||||
docs/
|
||||
...
|
||||
```
|
||||
|
||||
Граница файлов может уточняться по карте линковки. Нельзя объединять всё в
|
||||
один большой модуль: SDCC/линкер должны иметь возможность не тянуть лишний
|
||||
код и разнести холодные функции по банкам.
|
||||
|
||||
## 5. Состояние приложения
|
||||
|
||||
### 5.1. `ScApp`
|
||||
|
||||
В W2 хранится единственный объект состояния:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
ScPanel panels[2];
|
||||
uint8_t active_panel;
|
||||
uint8_t running;
|
||||
uint8_t video_mode_saved;
|
||||
uint8_t original_video_mode;
|
||||
uint8_t emm_block;
|
||||
uint8_t panel_page[2];
|
||||
uint8_t screen_page;
|
||||
ScJob job;
|
||||
int16_t last_error;
|
||||
} ScApp;
|
||||
```
|
||||
|
||||
Фактическое определение может отличаться, но следующие инварианты обязательны:
|
||||
|
||||
- `active_panel` равен 0 или 1;
|
||||
- обе панели ссылаются на разные EMM-страницы;
|
||||
- `job` либо неактивен, либо является единственным владельцем своих fd;
|
||||
- номера блоков и страниц обнуляются сразу после освобождения;
|
||||
- cleanup можно вызвать после любой частично завершённой стадии init.
|
||||
|
||||
### 5.2. `ScPanel`
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
char path[256];
|
||||
uint16_t count;
|
||||
uint16_t cursor;
|
||||
uint16_t top;
|
||||
uint32_t total_size;
|
||||
uint8_t source_kind;
|
||||
uint8_t phys_page;
|
||||
uint8_t truncated;
|
||||
uint8_t dirty;
|
||||
} ScPanel;
|
||||
```
|
||||
|
||||
Ограничения:
|
||||
|
||||
- `cursor < count`, если `count != 0`;
|
||||
- `cursor == 0`, если `count == 0`;
|
||||
- `top <= cursor`;
|
||||
- видимый курсор лежит в диапазоне `top..top+26`;
|
||||
- `count <= 640` для всей линейки 0.1–1.0;
|
||||
- `path` всегда завершён нулём.
|
||||
|
||||
## 6. Хранилище каталогов
|
||||
|
||||
Публичный интерфейс не возвращает указатель в W3:
|
||||
|
||||
```c
|
||||
int sc_store_get(const ScPanel *panel, uint16_t index, ScDirEntry *out);
|
||||
int sc_store_put(ScPanel *panel, uint16_t index, const ScDirEntry *entry);
|
||||
void sc_store_clear(ScPanel *panel);
|
||||
```
|
||||
|
||||
Для пакетных операций допускается закрытая функция, которая отображает
|
||||
страницу на короткое время. Она должна:
|
||||
|
||||
1. Сохранить текущую страницу W3.
|
||||
2. Подключить страницу панели.
|
||||
3. Выполнить только вычисления над памятью.
|
||||
4. Не вызывать ESTEX, renderer, банковский dispatch или пользовательский
|
||||
callback.
|
||||
5. Восстановить W3 перед возвратом.
|
||||
|
||||
Сортировка PoC должна быть нерекурсивной и не использовать callback `qsort`,
|
||||
чтобы избежать лишнего кода и банковских переходов. Предпочтительный первый
|
||||
вариант — Shell sort над одной отображённой страницей.
|
||||
|
||||
## 7. Чтение настоящего каталога
|
||||
|
||||
Последовательность `sc_source_fs_scan()`:
|
||||
|
||||
1. Проверить и нормализовать путь.
|
||||
2. Сохранить имя записи под курсором, если панель уже заполнена.
|
||||
3. Очистить счётчики панели, но не освобождать её страницу.
|
||||
4. При необходимости добавить синтетическую запись `..`.
|
||||
5. Выполнить `ffirst("*.*")/fnext` с одним общим 256-байтовым near-буфером.
|
||||
Шаблон `*` запрещён: P1 и P3 подтвердили, что DSS считает его именем без
|
||||
расширения и не возвращает обычные `NAME.EXT`.
|
||||
6. Пропустить `.` и `..` от DSS.
|
||||
7. Преобразовать `ffblk_t` в `ScDirEntry` и последовательно записать в EMM.
|
||||
8. При достижении 640 записей установить `truncated` и немедленно закончить
|
||||
scan: следующий `F_FIRST` сам переинициализирует глобальный итератор DSS.
|
||||
9. На стабильном DSS 1.71 код 35 означает, что закончилась единственная
|
||||
16-КБ страница кэша каталога, а не весь каталог. Принять уже прочитанные
|
||||
записи, установить `truncated` и добавить последней красную виртуальную
|
||||
запись `>>> MORE...` с флагом `SC_ENTRY_DSS_LIMIT`. Она занимает один
|
||||
слот, не является файлом и не участвует в файловых операциях.
|
||||
10. Отсортировать записи: `..`, каталоги, файлы, маркер ограничения; внутри
|
||||
обычных групп применить ключ и направление конкретной панели.
|
||||
11. Найти прежнее имя и восстановить курсор.
|
||||
12. Пометить панель и её экранные строки грязными.
|
||||
|
||||
Переборы двух панелей никогда не активны одновременно. `ffblk_t` не хранится
|
||||
в EMM и не является частью состояния панели. Причина и варианты снятия
|
||||
предела DSS разобраны в [dss-large-directories.md](dss-large-directories.md).
|
||||
|
||||
Флаг `SC_ENTRY_SELECTED` хранится в самой 24-байтовой EMM-записи и переезжает
|
||||
вместе с ней при сортировке. Резидентная `ScPanel` содержит только счётчик и
|
||||
32-битную сумму выбранных обычных файлов. `..` и виртуальные маркеры никогда
|
||||
не выбираются; refresh в текущем срезе 0.2 явно сбрасывает выбор и статистику.
|
||||
Одиночный `Insert` может менять флаг каталога, но все групповые операции
|
||||
`+`, `-`, `*` сначала отбрасывают `FA_DIREC` и никогда не меняют ранее
|
||||
установленный на каталоге флаг.
|
||||
|
||||
Маски `+`/`-` используют ASCII case-insensitive matcher с `*` и `?` только
|
||||
для файлов; специальный DOS-шаблон `*.*` совпадает также с именами файлов
|
||||
без точки. Редактор
|
||||
маски является общей координатной модальной поверхностью в BANK2: он рисует
|
||||
поле в экранной EMM-модели, поддерживает insertion, Backspace, Delete,
|
||||
Left/Right/Home/End и не создаёт BIOS-окно.
|
||||
|
||||
F6/F7 используют тот же editor и один validator компонента DOS 8.3. Локальный
|
||||
F6 не передаёт полные пути в DSS `RENAME`: отдельный банковый helper сохраняет
|
||||
CWD в резидентный scratch, входит в каталог панели, проверяет отсутствие
|
||||
target, вызывает `rename(old_basename, new_basename)` и восстанавливает CWD.
|
||||
Перезапись существующей записи запрещена до системного вызова.
|
||||
|
||||
F8 без выбора удаляет один объект после подтверждения: `unlink` для файла и
|
||||
`rmdir` для пустого каталога. Непустой каталог и виртуальная запись не
|
||||
запускают разрушительную операцию. При наличии выбора F8 одним
|
||||
подтверждением сначала удаляет выбранные файлы верхнего уровня, затем
|
||||
обходит все выбранные directory roots общей BFS-очередью, удаляет найденные
|
||||
файлы и выполняет `rmdir` каталогов обратным проходом. При ошибке или Esc
|
||||
уже выполненные действия не откатываются; CWD и EMM-страница освобождаются.
|
||||
|
||||
Групповой F5 проходит EMM-store по индексам и передаёт `sc_copy_begin/step`
|
||||
только записи с одновременно установленным `SC_ENTRY_SELECTED` и снятым
|
||||
`FA_DIREC`. Между файлами долговременный указатель W3 не сохраняется. При
|
||||
ошибке уже committed-файлы остаются, активный temp удаляется прежней job;
|
||||
это первая зафиксированная политика частично успешной группы.
|
||||
|
||||
F5 одного текущего каталога использует `sc_tree`: на время операции он
|
||||
выделяет отдельную EMM-страницу с BFS-очередью до 1008 каталогов. Узел хранит
|
||||
только parent-index, компонент DOS 8.3 и depth; путь восстанавливается перед
|
||||
действием. Первый 256-байтовый диапазон страницы хранит исходный CWD.
|
||||
Глубина ограничена 32, а путь — 255 символами. Файлы по-прежнему проходят
|
||||
через единственную 4-КБ copy-page. Existing target не merge-ится; target
|
||||
внутри source запрещается до создания root. Групповой F5 добавляет все
|
||||
выбранные каталоги верхнего уровня как независимые roots той же очереди
|
||||
после завершения выбранных top-level файлов.
|
||||
|
||||
## 8. Renderer
|
||||
|
||||
### 8.1. Интерфейс
|
||||
|
||||
Минимальные операции:
|
||||
|
||||
```c
|
||||
void sc_screen_clear(uint8_t attr);
|
||||
void sc_screen_cell(uint8_t x, uint8_t y, uint8_t ch, uint8_t attr);
|
||||
void sc_screen_text(uint8_t x, uint8_t y, const char *s, uint8_t attr,
|
||||
uint8_t max_width);
|
||||
void sc_screen_fill(uint8_t x, uint8_t y, uint8_t width,
|
||||
uint8_t ch, uint8_t attr);
|
||||
int sc_screen_scroll_rows(uint8_t x, uint8_t y, uint8_t width,
|
||||
uint8_t height, int8_t delta);
|
||||
void sc_screen_mark_rows(uint8_t first, uint8_t count);
|
||||
int sc_screen_flush(void);
|
||||
```
|
||||
|
||||
Экранный буфер — источник истины. Диалог рисуется в тот же буфер поверх
|
||||
панелей. Для PoC допустима перерисовка панелей после закрытия диалога вместо
|
||||
отдельного стека сохранённых окон.
|
||||
|
||||
### 8.2. Обновление
|
||||
|
||||
- Dirty mask занимает 32 бита в W2.
|
||||
- Изменение курсора помечает старую и новую строку.
|
||||
- Прокрутка панели помечает строки 1–27 её половины экрана.
|
||||
- При изменении `top` ровно на одну строку `sc_screen_scroll_rows()` сдвигает
|
||||
одновременно EMM-модель и экранный прямоугольник, затем renderer строит
|
||||
только открытую строку. PgUp/PgDn и большой скачок делают полный redraw
|
||||
области панели.
|
||||
- Смена каталога перерисовывает соответствующую половину и общие строки
|
||||
29–31.
|
||||
- Полный `sc_video_present_full()` используется после запуска, возврата из
|
||||
`.EXE` и крупных изменений.
|
||||
- `sc_video_present_rect()` группирует соседние грязные строки.
|
||||
|
||||
App-local WINREST wrapper проверен P1-пробой Commander: полный экран 80x32 и
|
||||
окно 20x2 из offset `0x2000` восстановились корректно. P2.1 затем подтвердил
|
||||
полную сверку через `RDCHAR`, оба направления прямоугольного scroll и 1000
|
||||
операций. Координатный backend принят как основной пакетный способ вывода;
|
||||
ограничения и артефакты описаны в
|
||||
[результатах P2.1](p2-screen-probe-results.md).
|
||||
|
||||
## 9. Ввод и команды
|
||||
|
||||
`sc_input.c` преобразует `getkey()` и `kbd_mod_state()` в `ScKeyEvent`:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
uint8_t ascii;
|
||||
uint8_t scan;
|
||||
uint8_t modifiers;
|
||||
} ScKeyEvent;
|
||||
```
|
||||
|
||||
Модификаторы хранят только Shift, Ctrl и Alt; состояния CapsLock, NumLock и
|
||||
Rus/Lat не участвуют в выборе команды.
|
||||
|
||||
P1 установил особенность DSS: при модифицированной клавише бит 7 raw
|
||||
scan-кода установлен. Например, Ctrl+R даёт `93`, Ctrl+PgUp — `D9`, Alt+F1
|
||||
— `BB`. `sc_input` нормализует их в `13`, `59`, `3B` через `scan & 0x7F`,
|
||||
но только после чтения compact modifiers. Таблица команд никогда не содержит
|
||||
raw-коды с битом 7.
|
||||
|
||||
`sc_command.c` превращает событие в `ScCommand`. Остальные модули не должны
|
||||
сравнивать scan-коды непосредственно. Команды описаны в
|
||||
[commander-keymap.md](commander-keymap.md).
|
||||
|
||||
В MAME `kbd_mod_state()` в момент возврата `getkey()` сохранил активный
|
||||
модификатор: compact Ctrl=`02`, Alt=`04`, Shift=`01`. Для переносимости
|
||||
управляющие ASCII-коды (`Ctrl+R` и подобные) всё равно имеют явные алиасы,
|
||||
не зависящие от live-состояния.
|
||||
|
||||
## 10. Копирование как конечный автомат
|
||||
|
||||
Состояния PoC:
|
||||
|
||||
```text
|
||||
IDLE
|
||||
-> VALIDATE
|
||||
-> CREATE_TEMP
|
||||
-> OPEN_SOURCE
|
||||
-> COPY_CHUNK <----+
|
||||
-> CLOSE_FILES |
|
||||
-> RENAME_TEMP |
|
||||
-> REFRESH |
|
||||
-> DONE |
|
||||
|
|
||||
COPY_CHUNK -----------+ пока done < total
|
||||
|-> CANCEL -> CLEANUP -> DONE
|
||||
+-> ERROR -> CLEANUP -> DONE
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- конечное имя проверяется до создания temp;
|
||||
- temp создаётся в целевом каталоге с `O_CREAT|O_EXCL`;
|
||||
- имя temp имеет форму `~SCxxxx.TMP` и перебирается с ограниченным числом
|
||||
попыток;
|
||||
- рабочая область находится в отдельной EMM-странице и временно отображается
|
||||
в W3; 4-КБ массива в W2 и изменяемого буфера в банке кода нет;
|
||||
- начальный логический размер блока — 4096 байт, чтобы между блоками часто
|
||||
обновлять прогресс и проверять отмену; после измерений он может быть увеличен
|
||||
вплоть до размера страницы;
|
||||
- чтение выполняется существующим `bank_read_page()` прямо из открытого fd в
|
||||
страницу; для записи нужен симметричный `bank_write_page()`, который сохраняет
|
||||
и восстанавливает отображение W3;
|
||||
- открыто не более двух fd;
|
||||
- успешность `write()` определяется его библиотечным контрактом, а не
|
||||
сырым DE ESTEX;
|
||||
- при short write оставшаяся часть блока дописывается или операция завершается
|
||||
ошибкой;
|
||||
- после успешного закрытия temp конечное имя проверяется повторно, и только
|
||||
затем temp переименовывается в него;
|
||||
- FAT date/time задаются temp через `PUT_D_T` перед close; после rename
|
||||
конечное имя получает исходные R/H/S/A через `ATTRIB`;
|
||||
- ошибка после rename помечается как ошибка финализации уже committed-файла,
|
||||
а не как полное отсутствие результата;
|
||||
- любое завершение проходит через единый cleanup;
|
||||
- после успеха перечитывается пассивная панель.
|
||||
|
||||
P19 добавляет policy существующего файла. Safe overwrite сначала переводит
|
||||
старый target в уникальный `~SBxxxx.BAK`, затем commit-ит temp. При отказе
|
||||
второго rename backup возвращается на место вместе с исходными атрибутами;
|
||||
после успешных метаданных нового target backup удаляется. `overwrite all` и
|
||||
`skip all` — локальное состояние одного F5. Readonly не наследует молча
|
||||
`overwrite all`, а повторно показывает choice-dialog.
|
||||
|
||||
Для версии 0.2 этот автомат расширяется очередью файлов, обходом каталогов,
|
||||
политиками overwrite/skip/rename и сохранением метаданных.
|
||||
|
||||
## 11. Запуск дочерней программы
|
||||
|
||||
Последовательность:
|
||||
|
||||
1. Завершить или запретить активный job.
|
||||
2. Сформировать проверенный абсолютный путь `.EXE`.
|
||||
3. Запомнить активную панель, курсоры и ожидаемый CWD; временно сделать путь
|
||||
активной панели текущим каталогом child.
|
||||
4. Выполнить синхронный ESTEX `EXEC` с `B=1` и сразу сохранить его результат
|
||||
до других вызовов libc.
|
||||
5. Восстановить ожидаемые диск и CWD.
|
||||
6. Повторно установить текстовый режим 80x32 и палитру Commander, не меняя
|
||||
сохранённую палитру для финального shutdown.
|
||||
7. Перечитать обе панели: внешняя программа потенциально могла изменить оба
|
||||
их каталога.
|
||||
8. Полностью вывести экран из screen page.
|
||||
9. При ошибке показать её штатной строкой состояния Commander.
|
||||
|
||||
P1 подтвердил контракт wrapper: C-сигнатура
|
||||
`p1_exec(uint8_t path_mode, const char *path)` получает mode в A и path в DE,
|
||||
затем перекладывает их в B и HL для ESTEX. `B=0` с коротким именем и `B=1`
|
||||
с явным относительным путём работают; probe получил `0x5A`. В Commander
|
||||
низкоуровневый `sc_platform_exec(path)` фиксирует `B=1`, принимает путь в HL
|
||||
по `__sdcccall(1)` и возвращает код завершения как `int` в DE.
|
||||
|
||||
### 11.1. Командная строка и `Ctrl+O` в 1.0
|
||||
|
||||
В VC 4.05/4.99 `Ctrl+O` переключает панели и user screen, а редактор `DosBuf`
|
||||
работает вместе с prompt и историей. Dos Navigator дополнительно показывает
|
||||
самостоятельный `TCommandLine` с горизонтальной прокруткой и историей. На Sprinter
|
||||
родительская DSS-shell приостановлена, поэтому её нельзя превратить в интерактивный
|
||||
фон простым скрытием панелей.
|
||||
|
||||
Поэтому 1.0 использует собственный pseudo-shell:
|
||||
|
||||
- однострочный prompt на экране панелей и полноэкранный режим делят один
|
||||
редактор, историю и модель текущего каталога;
|
||||
- `Ctrl+O` переключает режимы; возврат к панелям полностью строит их из
|
||||
`ScPanel`, поэтому отдельная копия экрана панелей не нужна;
|
||||
- builtin-команды минимум `cd`, смена диска, `dir`, `cls`, `pwd`, `help`;
|
||||
- внешняя команда запускается через подтверждённый ESTEX `EXEC`; после возврата
|
||||
pseudo-shell восстанавливает видеорежим, CWD и свою экранную модель;
|
||||
- вывод и скролл идут через тот же безоконный screen backend; прямой VRAM и BIOS
|
||||
window manager не используются;
|
||||
- полноэкранные ячейки могут повторно использовать текущую screen page. До
|
||||
фиксации поведения внешнего `.EXE` отдельной пробой проверяется `WINCOPY`.
|
||||
|
||||
## 12. Ошибки и владение ресурсами
|
||||
|
||||
Низкий уровень возвращает `0/-1` и устанавливает `errno`. UI не анализирует
|
||||
Carry Flag или регистры DSS.
|
||||
|
||||
Каждый ресурс имеет одного владельца:
|
||||
|
||||
| Ресурс | Владелец |
|
||||
|---|---|
|
||||
| общий EMM-блок PoC | `ScApp` |
|
||||
| физическая страница панели | `ScPanel`, без права освобождения блока |
|
||||
| экранная страница | `sc_screen`, без права освобождения блока |
|
||||
| рабочая страница копирования | `ScApp`/copy job, без права освобождения блока |
|
||||
| fd источника и temp | активный `ScCopyJob` |
|
||||
| исходный видеорежим | `ScApp` |
|
||||
| временно отображённая W3 | текущая функция storage/screen |
|
||||
|
||||
Cleanup выполняется в обратном порядке инициализации. Нельзя прятать
|
||||
владение fd или EMM-блоком в локальном static-состоянии модуля.
|
||||
|
||||
## 13. Предварительная карта кода
|
||||
|
||||
HOME/W2 содержит только постоянно необходимое:
|
||||
|
||||
- crt0, банковские трамплины и используемую libc;
|
||||
- init/cleanup;
|
||||
- главный цикл;
|
||||
- ввод и базовый dispatch;
|
||||
- EMM accessors;
|
||||
- screen flush;
|
||||
- критическую обработку ошибок.
|
||||
|
||||
Предварительные банки:
|
||||
|
||||
| Банк | PoC | Полная версия |
|
||||
|---:|---|---|
|
||||
| 1 | panel scan, sort, redraw model | панели, сортировки, режимы панели |
|
||||
| 2 | copy job, operation dialog | копирование/перемещение/удаление |
|
||||
| 3 | не нужен | меню, конфигурация, история |
|
||||
| 4 | не нужен | поиск и дерево |
|
||||
| 5 | не нужен | архивные/виртуальные источники |
|
||||
|
||||
Номер банка является деталью сборки, а не публичным ABI. Банковские модули
|
||||
не должны хранить долгоживущие payload-данные в локальном сегменте банка.
|
||||
Краткоживущий `ffblk_t` является обычным near-буфером; закреплять его в хвосте
|
||||
W1 бессмысленно, поскольку граница больших каталогов находится внутри DSS.
|
||||
|
||||
После каждой сборки проверяются:
|
||||
|
||||
- размер HOME вместе с DATA/BSS и heap;
|
||||
- запас каждого банка до 16 КБ;
|
||||
- отсутствие прямых вызовов в чужой банк;
|
||||
- расположение трамплинов;
|
||||
- стек W2 при всех вызовах ESTEX.
|
||||
|
||||
## 14. Эволюция после PoC
|
||||
|
||||
### Версия 0.2
|
||||
|
||||
- одностраничное хранилище остаётся ограничено 640 записями;
|
||||
- выбранные файлы и маски;
|
||||
- очередь операций;
|
||||
- рекурсивный обход;
|
||||
- local rename, mkdir, recursive group copy/delete выполнены; group move и
|
||||
защитная policy остаются;
|
||||
- транзакционная перезапись файлов и conflict-policy выполнены; directory
|
||||
merge и delete/move policy остаются;
|
||||
- атрибуты и время обычных файлов выполнены; метаданные каталогов — позже.
|
||||
|
||||
### Версия 0.4
|
||||
|
||||
- командная строка, история и полноэкранный pseudo-shell по `Ctrl+O`;
|
||||
- associations/user menu;
|
||||
- внешний viewer/editor/setup;
|
||||
- конфигурационный файл;
|
||||
- переключение дисков.
|
||||
|
||||
### Версия 0.6
|
||||
|
||||
- quick view и info source;
|
||||
- дерево и поиск;
|
||||
- фильтры и сравнение каталогов;
|
||||
- подсчёт размеров каталогов;
|
||||
- описания файлов.
|
||||
|
||||
### Версия 0.8+
|
||||
|
||||
- `SC_SOURCE_SEARCH` как виртуальная панель;
|
||||
- `SC_SOURCE_ARCHIVE`;
|
||||
- протокол внешних helper-программ;
|
||||
- мышь, помощь, локализация;
|
||||
- измеренная оптимизация кэшей и renderer.
|
||||
|
||||
### Версия 2.0+
|
||||
|
||||
- динамическое выделение EMM-страниц панели;
|
||||
- предел 1024/2048 или отсутствие продуктового предела после измерений;
|
||||
- межстраничная сортировка без полной копии каталога в W2;
|
||||
- деградация при нехватке EMM и тесты частично выделенных цепочек страниц.
|
||||
|
||||
## 15. Архитектурные запреты
|
||||
|
||||
- Нельзя вызывать DSS при стеке вне W2.
|
||||
- Нельзя держать одновременно восемь файловых дескрипторов.
|
||||
- Нельзя сохранять W3 pointer в состоянии приложения.
|
||||
- Нельзя менять W1 вручную из банковской функции.
|
||||
- Нельзя вызывать чужой код при оставленной временной странице W3.
|
||||
- Нельзя выполнять разрушительную операцию без известного cleanup-path.
|
||||
- Нельзя смешивать вывод через `printf` с экранной моделью во время работы UI.
|
||||
- Нельзя добавлять функцию DN только потому, что она присутствует в DN;
|
||||
каждая функция должна соответствовать roadmap Commander.
|
||||
Reference in New Issue
Block a user