Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Блоги > Dima_DPE

Оценить эту запись

Макросы Haxe. Автоматическое встраивание ресурсов (assets embedding) 3.

Запись от Dima_DPE размещена 22.05.2013 в 02:17

В прошлый раз я не смог показать встраивание шрифтов из ttf файлов. Скажу честно, меня это сильно расстроило, и я провел небольшое расследование и нашел рабочий метод. Поэтому и появилась третья статья про встраивание ресурсов.

Но первое, что мы сделаем, будет автосборка. Заменим наше прошлое определение класс Assets:

Код AS3:
@:build(deep.macro.AssetsMacros.embed("../assets")) class Assets {}
на более короткую и менее понятную:

Код AS3:
class Assets implements IAssets<"../assets"> { }
Класс Assets остался, но только теперь он реализует некий интерфейс IAssets, да еще и параметр какой-то непонятный, переданный в роли дженерика, равный нашей папке из прошлого примера. А все дело в очередной магической мета дате - @:autoBuild объявленной в интерфейсе IAssets:

package deep.macro;

Код AS3:
@:autoBuild(deep.macro.AssetsMacros.embed()) interface IAssets<Const> { }
Не знаю, что вы на этот раз воскликните. Но да, это почти тотже @:build, но без параметров в которых был бы указан путь к папке с ассетами. Значит, что у нас вышло. У нас есть интерфейс с указанным макросом в автобилде и класс, который реализует этот интерфейс. Все это заставит компилятор выполнить наш макрос по разу для каждого класса, реализующего наш интерфейс. В роли интерфейса может выступить и класс. В документации сказано, что autoBuild добавит всем наследующим классам @:build мета тег с аналогичным содержимым.

Польза от @:autoBuild понятная - более короткая запись наследующих классов и автоматическое добавление мета тега. Минусов не знаю, разве что можно в ветке иерархий забыть и/или не заметить, какие билд методы вызываются.

Но мы забыли один важный момент. А именно путь к папке ассетов. Если раньше мы передавали его в виде строки, то теперь он спрятан в параметре родительского интерфейса. И нам придется найти способ прочитать это значение из макроса. И вот тут начнется самое интересное.

Метод Context.getLocalClass() вернет нам Type (haxe.macro.Type) текущего класса, в нашем случае Assets, а внимательно изучив Type, мы сможем получить и искомую строку. Сказать по правде, Context.getLocalClass() вернет Ref<ClassType>, где Ref<T> это обертка, с одним методом get():T (еще есть toString, но о нем не сейчас). Обертка эта нужна, чтобы не давать доступ сразу к экземпляру класса T, а лишь по требованию - get(), тем самым уменьшает нагрузку и создает экземпляры ClassType только по необходимости. Весь метод получения пути из параметра интерфейса выглядит вот так:

Код AS3:
static function getPath(type:ClassType):String {
 
	for (i in type.interfaces) {
		if (i.t.toString() == "deep.macro.IAssets") {
			switch (i.params[0]) {
				case TInst(t, _) : 
					var ct:ClassType = t.get();
					switch (ct.kind) {
						case KExpr( { expr:EConst(CString(s)) } ): return s;
						case _: throw "assert";
					}
 
				case _: throw "assert";
			}
		}
	}
	throw "assert";
}
Внимательно изучим данный метод. Уж больно он “крутой” получился. ClassType - это огромная структура, лучше взглянуть на нее самим. Но нас интересует лишь список интерфейсов - type.interfaces. Переберем все интерфейсы и найдем тот, который deep.macro.IAssets. Далее взглянем на параметры интерфейса, а точнее на его единственный параметр - i.params[0], мы точно знаем что он строка, точнее мы хотим, чтобы он был строкой. Не сложно понять, что он будет выглядеть примерно так:

Код AS3:
TInst(Ref<KExpr({expr:EConst(CString(S)), pos:position})>)
Кому сложно, сделает просто trace и сам все увидит. Если бы не Ref в TInst, можно было бы это записать в один case (спасибо крутому pattern matching из Haxe 3), но пришлось сделать в два switch-а. Сначала получив Ref<ClassType>, а потом уже распарсив весь ClassType в один case - case KExpr( { expr:EConst(CString(s)) } ). В последней s и будет храниться наша строка “../assets”. Остальные случаи для нас ошибочны, поэтому я вставил там “подробный” throw “assert”, как обычно и делает Николас в таких местах.

Согласен, было проще, но теперь вы знаете, чего можно добиться простым методом Context.getLocalClass(), при том, что он всегда возвращает именно тот тип, в котором вызван макрос. А вот и пример использования метода getPath:

Код AS3:
var ref:ClassType = Context.getLocalClass().get();
var path = getPath(ref);
И я снова считаю, что получилось отлично. Да, немного громоздко, зато универсально. Если вы запустите проект сейчас, то все заработает как и раньше, главное, убрать у функций embed обязательный параметр path.

Ну, и напоследок вернемся к шрифтам. Здесь нужно сделать небольшое, но важное отступление.

Раньше мы встраивали ассеты с помощью хака, когда в тело меты вписывался весь контент ассета после префикса “data:”. Так вот, для шрифтов он не работает. Поэтому было решено отказаться от трюка, и во все мета даты записывать только путь к файлу, это позволит унифицировать код для шрифтов, битмап и звуков, да и сам отказ от хака только плюс.

В Haxe 3 появился удобный метод встраивания шрифтов для flash проектов. Точнее удобная мета дата:

Код AS3:
@:font("path.ttf", range="")
Если с первым параметром все понятно, это обычный путь к файлу, то вот второй параметр интереснее, он вроде как не обязателен, но вряд ли кто-то решится встраивать весь шрифт целиком. А для остальных случаев нужно передать строку типа "a-z0-9", которая укажет, какие именно символы встраивать во флешку.

Для небольшого упрощения, предположим, что для всех шрифтов вам нужен один набор символов. И сделаем макро метод, для задания этого набора:

Код AS3:
static var fontsRange:String = "a-zA-Z0-9.,;:'\&quot;`@#$%^&*()[]{}";
 
macro static function setFontsRange(range:String) {
	fontsRange = range;
	return macro null;
}
Макрос просто изменит статическую переменную fontsRange, внутри AssetsMacros класса и вернет macro null. Т.е. вызов макроса в результате заменится на null; Можно туда же вернуть 0; Но вот как совсем ничего не возвращать макросом я не нашел, да и почти уверен, что нет такого.

Все, что нам осталось - это собрать мета параметры, т.к. большинство утилитарных методов было адаптированны под AFont еще в коде для предыдущей статьи. Возьмем то, что было в прошлый раз, точнее почти то же, но без “data:”, а просто путь к файлу. И добавим fontsRange если у нас шрифт и если fontsRange задан:

Код AS3:
var metaParams = [ macro $v{file} ];
if (type == AFont && fontsRange != null)
	metaParams.push( macro $v{fontsRange} );
Теперь, для fontsRange = "a-zA-Z0-9.,;:'\"`@#$%^&*()[]{}" мы увидим такую картину:



Обратите внимание, что в PT Sans остался 161 символ, т.к. мы взяли его из другого swf. А вот Arial импортировали из arial.ttf 86 символов. Важно: нужно бы импортировать и символ пробела, иначе пробелов в тексте вам не видать. Ну а теперь выполнив:

Код AS3:
var f = Assets.arial;
trace([f.fontName, f.fontStyle]);
мы увидим [Arial, regular]. Именно то, чего мы и ожидали.

Ура! Мы это сделали. Теперь встраиваются и шрифты, и картинки, и звуки, и текстовые файлы. Правда, во время тестов обнаружилась одна проблема - не удается указать интервалы символов отличных от латинских, но это, видимо, ограничение neko работы с utf-8 строками и если кто-то вкурсе как это обойти, то подскажите или сделайте pull request на гитхабе.

На этом я снова остановлюсь, и на этот раз точно. Я убедил себя, что рекурсивная обработка нам ни к чему, так же как встраивание бинарных файлов. Сегодня вы научились работать с новым мета тегом autoBuild, узнали кое-что новое о haxe.macro.Type и его содержимом. И главное, что теперь работает встраивание шрифтов из ttf файлов.

Исходный код всего урока лежит на гитхабе.

Цитата:
Отдельная благодарность Александру Хохлову и Александру Кузьменко за помощью в написании статьи.
Всего комментариев 1

Комментарии

Старый 17.06.2013 01:35 wvxvw вне форума
wvxvw
 
Аватар для wvxvw
Я тут недавно, скорее вшутку, написал рекурсивный макрос, который генерирует вложенные циклы с какой-то математикой.
Я бы не сказал, что это самый лучший пример использования макросов... Но в качестве примера типичной задачи и нетипичного решения - я думаю можно было бы сделать
 

 


Часовой пояс GMT +4, время: 13:00.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2024, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.