Макросы Haxe. Автоматическое встраивание ресурсов (assets embedding) 2.
Запись от Dima_DPE размещена 19.05.2013 в 20:13
Недолго думая, я решил, что следующая статья будет продолжением предыдущей, так что, если вы еще не читали ее и не открывали проект из той статьи, пора это сделать. А все потому, что так мне будет легче показать некоторые важные моменты, которые на простых макросах не показать.
Первым делом, мы попробуем автоматизировать макрос для автодополнения. Автодополнение выполняет все макросы, чтобы получить точные данные о всех полях класса. Но в случае автодополнения часть операций макроса можно опустить. Макрос может сам узнать, запущена сейчас компиляция или просто автодополнение:
Context.defined принимает один параметр, который может быть передан компилятору при помощи -D define_name или задан самим компилятором автоматически. Например для js сборки Context.defined(“js”) вернет true. Context.defined("display") вернет true, когда выполняется автодополнение.
Остается понять, что в макросе не надо выполнять когда работает автодополнение и исключить это. Первое, что приходит на ум - это чтение всего содержимого файла и запись его в тело меты. По идее если убрать это чтение, то это никак не повлияет на автодополнение и лишь увеличит скорость выполнения макроса в целом. Сделать это просто:
var display = Context.defined("display"); … var data = display ? null : File.getContent(file);
Мы уже сделали немало, но давайте пойдем дальше. А дальше мы немного углубимся в macro reification. Для начала начнем с нового синтаксиса:
Обратите внимание на двоеточие после macro. Теперь complexType хранит в себе один из консрукторов enum-а ComplexType (haxe.macro.Expr), в данном случае TPath(p:TypePath):
“Ага!” - скажете вы, - “Да это же почти то же, что мы делали “руками” в методе getKind.” Ну так давайте использовать это. Для начала, правда, сделаем дополнительную функцию для вычисления комплексного типа, которая пригодится дальше и изменим метод getKind:
// комплексный тип static function getComplexType(type:AssetType):ComplexType { return switch (type) { case AImage: macro : flash.display.BitmapData; case ASound: macro : flash.media.Sound; } }
// Базовый тип static function getKind(type:ComplexType):TypeDefKind { return switch (type) { case TPath(p): TDClass(p); default: Context.error("can't find asset type", Context.currentPos()); null; } }
Не скажу, что новый getKind стал сильно красивее предыдущего, но мне он такой больше нравится. Меньше страшных и пугающих фигурных скобок и enum-ов, но все равно мы от них не избавились и не сможем.
Поехали дальше. Попробуем заменить:
на “реифицированный” вариант:
Ура, работает! На самом деле странно, т.к. у нас Position самодельный, а в macro нельзя указать position и он берет Context.currentPos(). Но, т.к. position учитывается только при дебаге, а мы вряд ли будем дебажить ассеты, то проблем не будет. А кто не понял, что за $v{} в коде, обратитесь к коду первой статьи и дополнительным методам, о которых я писал во второй статье.
Вернемся теперь к методу getComplexType. Я ведь говорил, что он нам еще пригодится. Улучшим наше автодополнение ещё раз. Кто запускал прошлый пример, тот видел, что старое автодополнение напротив атрибутов Assets писало тип Unknown<0>, теперь же настало время вписать туда более внятный тип:
var ct = getComplexType(type); … kind : FVar(ct, { expr : ENew( { pack : ["assets"], name : getPrefix(type) + name, params : [] }, getArgs(type)), pos : pos } ),
Самое время отказаться от префиксов в названиях переменных, правда для классов я решил их все таки оставить и сделать все немного красивее:
От префиксов, как видно, я не отказался полностью, а оставил их там, где без них не удалось бы получить валидный идентификатор (snd1, snd2). Можно, конечно, еще проверять на уникальность, но пока мы работаем с одним каталогом, файловая система делает это за нас.
Еще можно заинлайнить утилитарные методы, такие как getMetaName или getComplexType, что еще немного ускорит работу макроса.
Напоследок, я собирался сделать встраивание шрифтов, но не тут то было. Закралась ошибочка, с которой я не смог справится. Но Haxe не был бы Haxe-ом, если бы мы не могли решить задачу иначе.
Допустим, что у вас шрифты встроены уже в готовую флешку, если это не так, то можно взять hxswfml и быстренько это сделать. Да и вообще, давайте уже сделаем встраивание флешек автоматически, без этих долгих -swf-lib в hxml файле. И на этот раз это займет всего три строчки кода:
Ну, или четыре, если считать скобки. Compiler.addNativeLib - новый для вас метод, который встраивает swf или swc, точно так же, если бы мы написали -swf-lib file. Класс haxe.macro.Compiler вообще очень полезный и интересный, стоит заглянуть в него и почитать документацию по нему.
Осталось встроить текстовые файлы, и на этом, думаю, хватит. С текстовыми файлами вообще просто. Создаем переменные типа String и сохраняем в них содержимое файла:
if (type == AText) { res.push( { name : varName(type, p.file), access : [APublic, AStatic], doc : 'file: "$file"', kind : FVar(ct, macro $v{data} ), pos : pos, }); continue; }
Теперь точно все. Сегодня мы больше всего времени уделили автодополнению и это понятно. Макросы были бы обычными шаблонами, если бы не столь тесная работа с компилятором. Еще мы оптимизировали работу макроса и добавили встраивание текстовых и swf(swc) файлов. Все это делает из нашего пробного макроса вполне юзабельный инструмент для работы над flash проектами.
Исходный код всего урока лежит на гитхабе. На самом деле, я многое изменил и доработал, о чем я не писал в статье по ряду причин, так что взгляните на весь макрос в целом.
Цитата:
Отдельная благодарность Александру Хохлову и Александру Кузьменко за помощью в написании статьи.
Всего комментариев 2
Комментарии
20.05.2013 22:17 | |
Магия.
|
Последние записи от Dima_DPE