Разработка программ и игр для такого довольно чувствительного к эффективности кода компьютера как ZX Spectrum имеет свои особенности. Существующие технологии
для высокоуровневой разработки под Z80 вообще предоставляют очень мало возможностей для оптимизации. Посмотрим что может нам предложить в этом смысле среда ZXDev. Возьмём маленькую программу и попробуем её оптимизировать.
Это будет незабвенная LaserDemo, переведённая с ZX-Basic. Оберон-вариант выглядит так:
Код: "OBERON"
MODULE LaserDemo; (*$MAIN*)
(* Laser Basic Demo for Sinclair ZX Spectrum, 48 Kb. *)
(* Copyright (C) 2012 Oleg N. Cher, VEDANTA software. *)
IMPORT L := Laser, B := Basic, Rsrc := LaserSprite2B;
(* Пример взят из книги "Как написать игру для ZX Spectrum" *)
(*
10 BORDER 0: PAPER 0:INK 5
20 CLS: LET S=-2
30 FOR N=7 TO 10
40 .R0W=5:.C0L=S:.SPN=N
50 .PTBL
60 PAUSE 5
70 NEXT N
80 LET S=S+2
90 IF S<32 THEN GO TO 30
*)
PROCEDURE Main* ;
VAR
n, s: SHORTINT;
BEGIN
B.Init; L.InitSprites(Rsrc.SprStart, Rsrc.SprSize);
B.BORDER(B.Black); B.PAPER(B.Black); B.CLS;
FOR s := -2 TO 30 BY 2 DO
FOR n := 7 TO 10 DO L.PTBL(s, 5, n); B.PAUSE(5) END;
END;
B.Quit;
END Main;
END LaserDemo.
Сейчас LaserDemo.tap занимает 10090 байт. Оптимизировать мы будем по размеру кода, потому что быстродействия нам хватает — и так приходится затормаживать бег чудика паузой.
Такой действительно огромный по меркам Спектрума размер вызван двумя моментами.
- Код использует спрайты с номером от 7 до 10 (получается 4 фазы бега), включен же весь набор Sprite2B.
- К коду подключен весь рантайм Laser Basic'а, т.е. все неспользуемые процедуры. Это не неизлечимый диагноз, а просто ещё не сделанный кусок работы.
По первому пункту мы можем сделать набор спрайтов "смартлинкуемым". Но поскольку ни компилятор, ни линкер никак не могут догадаться по номерам спрайтов какие именно включать, а какие нет. Это никак нельзя сделать. Это можно было бы сделать, если бы процедура Laser.PTBL принимала на вход не номер спрайта, а адрес, но не будем сейчас менять логику её работы. Мы просто вручную выдерем нужные нам спрайты чудика (7-10) и включим в модуль только их.
Это можно сделать, подготовив "пустой" модуль, тело которого будет написано на Си (так сейчас устроен модуль LaserSprite2B). Либо же опишем данные прямо из Оберон-модуля с помощью процедуры Basic.DATA. Мне больше нравится второй способ, хотя он немного (на несколько байт) больше по размеру — за счёт "перепрыгивающей данные" процедуры Baisc.DEFDATA — чтобы данные не начали исполняться как код. Так что здесь самая большая трудность — найти в LaserSprite2B где именно в спрайт-данных находятся спрайты чудика.
Нам поможет знание формата хранения спрайтов. Первым идёт номер спрайта (1-255), потом адрес (два байта), второй (старший) из которых должен быть от #C6 до #DC. Дальше идут ширина и высота в знакоместах. Значит ищем в файле LaserSprite2B.c спрайт №7 по шаблону "0X07", после которого через байт должно быть число от #C6 до #DC. Ага, между прочим, нашли. Это байты:
0X07,0XFE,0XCB,0X05,0X04,0X00,0X00,0X00,0X00 ...
Добавим их в нашу программу. Теперь мы можем отказаться от использования модуля LaserSprite2B и всего набора спрайтов из него.
Я тут хотел сэкономить немножко байт за счёт отказа от атрибутов (всё равно чудик весь жёлтый), но Laser Basic хранит адреса, и их приходится настраивать процедурой инициализации спрайтов. А процедура расчёта адресов конечно ориентируется только на спрайты с атрибутами. Так что упс.
Суть сделанной оптимизации показывает
этот коммит. LaserDemo.tap похудел до 4617 байт, из которых чудик занимает 740.