В процедуре dzx0 есть локальная переменная last_offset, которая задает смещение в буфере, откуда брать данные (см. сишный сорец). Обычно когда мы пишем код на ассемблере, мы не думаем о том, где держать переменные и кладем их куда-нибудь в статическую ячейку памяти поблизости. А в ivagor-овском dzx0 локальная переменная сделана по-взрослому локальной, расположенной на фрейме стека, откуда она достается с помощью довольно редко встречающейся инструкции XTHL. Так бы я и не заметил, но здесь если бы переменная была глобальной, адаптировать процедуру для нескольких контекстов было бы значительно труднее.
LDIR - это инструкция Z80, которая позволяет одной инструкцией скопировать кусок памяти. В версии для 8080 на ее месте подпрограмма с циклом. Это тоже оказалось удобно, потому что если бы у нас была инструкция LDIR, мне пришлось бы ее заменять на подпрограмму самому.
Ъ это просто потому что XTHL такая очень редкая и трудная для понимания инструкция с адской мнемоникой и твердый знак добавляет ей карикатурного анахронизма. Редкий случай, когда я считаю, что у z80 получилось лучше и проще -- EX (SP), HL.
- - - Добавлено - - -
Планировщик -- это часть программы, которая вызывает задачи. Во взрослой ОС он был бы в ядре и немного посложнее. Тут он просто перебирает все задачи подряд по кругу.
Когда задача считает, что ее дело сделано, она вызывает yield. yield сохраняет все регистры в контексте задачи и возвращается в планировщик.
Планировщик переходит к следующей задаче -- берет ее контекст (это указатель стека), восстанавливает состояние процессора, делая pop всем регистрам со стека и исполняет ret. И так далее.
С точки зрения каждой задачи, она просто делает волшебный вызов "call yield". Когда он возвращается, она продолжает свое исполнение как ни в чем не бывало.
Теперь на более высоком уровне, почему я это упомянул. Каждое переключение задачи обходится дорого, потому что нужно сохранить контекст одной задачи и восстановить контекст следующей. Лучше их делать как можно реже. Поэтому я изначально надеялся делать это например после подпрограммы LDIR. Но это оказалось плохой идеей потому что потоки распаковываются неравномерно и совершенно непонятно, как их потом сводить в один.
На самом деле я уже знаю, как это сделать более оптимально, хотя это необязательно будет хорошо с точки зрения монотонности фреймрейта. Идея очень простая:
- LDIR уступает всегда на границе 16 байт
- в основном коде строка регистров AY собирается прямо из буферов. Текущая строка = номер кадра, и перебираем столбцы.
Дальше есть варианты. Можно просто вызывать планировщик один раз в 16 кадров. Нормально, если есть возможность пожертвовать значительной частью кадра.
Другой вариант -- вызывать по одной задаче на кадр. С задержкой в 16 кадров у нас будут все те же самые строки в буферах, но на каждый кадр времени будет уходить значительно меньше. Еще два кадра останутся пустыми, или можно еще пару каких-нибудь интересных потоков туда запихать.
Казалось бы, не проще бы было уж на этом этапе сделать обычный pt3-плеер? =)





Ответить с цитированием