top4top - перемены
Несколько дней назад на балконе нашего офиса на базе недостроенного особняка-театра произошло историческое собрание всех. Было сказано много всего, про себя могу сказать что скорее всего в течении месяца-двух сменю место работы.








Профилирование ActionScript 3.0 проекта
Если Вы не раз профилировали проект, то что-то интересное, возможно, найдете ближе к концу поста.
Вступление
На данный момент единственный инструмент для профилирования это Flex Builder Profiler (хотя необходимый профайлеру API открыт - делай кто хочешь).
В процессе разработки top4top.ru не раз возникали проблемы с утечками памяти, иногда для их устранения приходилось даже вызывать разработчиков в офис по ночам. Иногда память не освобождалась вообще - росла с шагом 50-200 mb от числа переходов по страницам:

Это очень заметно и чинятся, как правило, за несколько часов. Сложнее сказать, есть ли утечка памяти, и где она по такому графику:

Тут уже мало что понятно. Поэтому первый вопрос - как определить, есть ли в приложении утечка памяти.
Определение факта утечки памяти - общие моменты
Тут нам очень поможет функция взятия разности между двумя dump-ами памяти приложения:

Результат - таблица Loitering Objects View, цитата из хелпа:
The Loitering Objects view shows you the differences between two memory snapshots of the application that you are profiling. The differences that this view shows are the number of instances of objects in memory and the amount of memory that those objects use. This is useful in identifying memory leaks. The time between two memory snapshots is known as the snapshot interval.
Для нас в ней самое интересное это имена классов и количество их оставшихся экземпляров.

На схеме выше приведен первый приходящий в голову вариант по поиску memory-leak-ов на Главной Странице. Однако он не работает - в список “бездомных” объектов попадают не только лики с Главной страницы, но и все объекты, создаваемые в Профайле 1.
Определение факта утечки памяти - стратегии
Можно придумать много разных стратегий. На картинке ниже прямоугольниками обозначены состояния системы, в которых инстанциируются объекты. При уходе из состояния его объекты должны удаляться. У нас в top4top состояния были страницами сайта, иногда - разделами (главная, чат).

Устранение утечки
- В простых случаях достаточно просмотреть код остающихся в памяти классов для устранения ошибки
Если она не очевидна, то тут нам поможет окошко Object References, которое показывает все ссылки на объект и открывается по дабл-клику на элементе в таблице Loitering Objects:

Сложнее всего искать утечки, из-за которых не удаляется вообще ничего. В этом случае два предыдущих способа не работают.
Именно такая ситуация у нас и была. Утечка находилась в часто исползуемом визуальном компоненте (назывался HtmlText). При создании компонента-страницы LayoutIndex в него где-то глубоко добавлялся экземпляр HtmlText и с тех пор вся иерархия (даже после удаления компонента всей страницы из дисплей-листа) становилась неуязвима для Garbage Collector’а.

Способ борьбы тут оказался довольно простой:
- package
- {
- import flash.display.Sprite;
- import flash.events.Event;
- public class test extends Sprite
- {
- private var picture:Picture;
- private var mainText:HtmlText;
- private var enterButton:SomeButton;
- private var closeButton:AnotherButton;
- public function test()
- {
- initContent();
- }
- private function initContent():void
- {
- picture = new Picture();
- addChild(picture)
- // Дальше в том же духе инициализируем и
- // добавляем другие компоненты
- // И подписываемся на удаление со сцены
- addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true);
- }
- // Удаляем все ссылки на дочерние элементы
- private function onRemovedFromStage(event:Event):void
- {
- picture = null;
- mainText = null;
- enterButton = null;
- closeButton = null;
- while (numChildren > 0)
- removeChildAt(0);
- }
- }
- }
Таким образом в памяти остаются только “текущие” объекты и мы с легкостью с помощью таблицы Loitering Objects их удаляем.
Делать подобные удаления всегда или добавлять этот код только в процессе устранения ликов - это отдельное решение.
Также у нас действует стандарт, что все подписки на события должны быть объявлены с Weak Reference, если нет четких противопоказаний. За весь проект случаев, когда нужна была подписка с жесткой ссылкой было всего несколько. false, 0, true!
Напоследок - регулярное выражение для поиска не-weak подписок: addEventListener\(([\w.\"]*), (\w*)\) (автор: Алексей «Vooparker» Аникутин).
top4top - процесс разработки
Периодически фотографирую интересные моменты нашей работы над порталом top4top.ru, вот два из них:
Алексей «Vooparker» Аникутин, один из авторов yarovoy.com фиксит проблемы upload-а файлов на Mac (если сервер использует переадресацию после окончания загрузки, то на Mac это не работает):

Олег, JAVA-программист, пришел на работу прямо с прибывшего утром проезда:

Диаграммы классов ActionScript 3.0 и Flex - постеры.
Многие в курсе, но тем не менее: в далеком 2006-ом появились диаграммы классов по ActionScript 3.0 и Flex 2 в виде PDF документов, с которых можно напечатать постеры. Фрагмент Flex-диаграммы взят из поста “Ted On Flex”:

ActionScript 3.0 Class Diagram на Flex.org
Flex 2 Diagram на Flex.org
VooDoo подсказывает: Вышеперечисленные + еще куча постеров (Flex 3, AIR)
Напечатали ActionScript 3.0 диаграмму на цветном принтере на А3 с максимальным качеством, получилось мелко, видно только вплотную. Так что если есть возможность, лучше печатать на А2 или А1. На А3 со стандартным качеством вообще ничего не разобрать.
Баги в средствах разработки Flash Platform
Баги есть везде (для справки: шрединбаг, мандельбаг, борбаг), в том числе в mxmlc и Adobe Flex Builder 3. Попробую перечислить опасные и поедающие время баги - а это обычно те, которые абсолютно нелогичны (Ваши варианты в комментах).
- Ранний return в функции: если в начале функции поставить return, то в некоторых случаях это ведет к обрушению компилятора.
Memory-leak при использовании локальной переменной в конструкторе класса - она становится членом класса (гейзенбаг, сейчас повторить не удалось. Источник - Игорь “peace").
- public function ClassConstructor()
- {
- // Иногда при подобном объявлении у экземпляров
- // класса будет появляться поле clip и данный объект
- // не будет собран Garbage Collector'ом.
- var clip:MovieClip = new MovieClip();
- }
Если попробуете запустить профайлер на проект из 1 класса с одной строкой похожего кода в конструкторе то получите Runtime Exception c упоминанием некоего faramir’а
При использовании Embed-метатегов для включения .SWF-файлов в проект встроенные в этот файл шрифты часто (не при каждом запуске одного и того же приложения) заменяют собой аналогичные встроенные в родительский проект.
Например, если у Вас в asset-ах проекта есть надписи статическим текстом со сглаживанием, то при его embed-е Вы сможете пользоваться только теми буквами, что были включены в тот шрифт
(Источник - Филлип “FSB” Бондарев)При загрузке внешнего .SWF-файла с заранее (во Flash IDE) созданными там динамическими текстовыми полями Вам не удаться использовать в них встроенный шрифт основного ролика (Источник - Филлип “FSB” Бондарев).
Это справедливо также для случая, когда Вы вытаскиваете класс символа с существующими там динамическими текстовыми полями через ApplicationDomain.getDefinition() из загруженного файла и инстанциируете его уже в главном проекте
Подборка багов из Adobe Bug and Issue Tracking System
Ошибка компилятора:
- var num = 0.0.8;
Ошибка компилятора:
- function read():int
- {
- return 0;
- }
- function mouseClick(event:MouseEvent):void
- {
- var i: int;
- switch (i = read())
- {
- default:
- do
- {
- i = read();
- }
- while (i != 0);
- trace("OK");
- }
- }
Превращение в бесконечный цикл:
- function makeMeMyError():void
- {
- var a:Array = [];
- var i:uint = 0;
- while (false)
- {
- a[i++]=0;
- continue;
- }
- }
Ошибка компилятора:
- switch (param)
- {
- case 'one' :
- // the following line throws an #1010 Error
- var operations1:XML = company.department.(@title == "Operations")[0];
- trace(operations1);
- break;
- case 'two' :
- // no crash here
- var operations2:XML;
- operations2 = company.department.(@title == "Operations")[0];
- trace(operations2);
- break;
- }
- Пустой свич - ошибка компилятора:
- switch (i)
- {
- }
И финальный аккорд: обрушение браузера / виртуальной машины.
- "".lastIndexOf(null);
P.S: Я рассмотрел только баги компилятора и багоподобные особенности виртуальной машины. Issue по Flex Builder (их сейчас 795) и по другим продуктам к Вашим услугам всегда - Adobe Bug and Issue Tracking System.
Спасибо за наше счастливое детство

Новая версия движка
Все себе попеременял в блоге, если читаете через RSS - буду рад если зайдете посмотреть. Теперь можно изменить padding какого-нибудь блока без давления на мозг Кости
Ура!
Костя, большое спасибо.
Приглашение новым авторам, feedback.
Уважаемые друзья, если у Вас есть что написать, мы будем рады принять Вас в свою компанию riapriority.com. Если у Вас есть тема для поста - пишите, и я попробую ее осветить как смогу ![]()
Использование модулей
Проблема: как загружать код только тогда, когда он нужен? Например, на flash-сайте есть игры, но пользователь может до них и не дойти - поэтому их надо сделать подгружаемыми по требованию.
Решение: Flex project для этого есть модули.
Решение: ActionScript project можно банально подгружать другие .swf и вызывать публичные методы и свойства загруженных объектов, если заранее знать, что они там есть (при соблюдении Security). Мы получаем нетипизированный доступ, со всеми вытекающими из этого негативными последствиями:
- // Вы загрузили игру "Шашки" и теперь хотите ее начать за белых
- (loader.content as Object).start(true);
Есть простой способ сделать внешние модули строго типизированными с богатой историей использования. Пусть мы имеем отдельную .swf с нардами - backgammon.swf, главный класс - Backgammon имплементирующий IBackgammon). В основном проекте оставляем лишь IBackgammon, а сам код игры выносим в отдельный проект.
- // Вы загрузили игру "Нарды" и теперь хотите начать инициатором
- var iBackgammon:IBackgammon = IBackgammon(loader.content)
- iBackgammon.start(true);

Вот в кратце и все. Далее полученный при компиляции игры .swf загружаем во время выполнения (в runtime).
Дополнительно такими манипуляциями мы кроме размера основного .swf уменьшим время компиляции основного проекта.
Особенности: если вы не исключите интерфейс IBackgammon из бинарного файла модуля, то у вас наверняка возникнут ошибки при приведении загруженных данных к этому интерфейсу.
Кстати, в сами нарды вы можете поиграть на нашем портале top4top, в разделе общение. Там вам придется сначала найти себе собеседника и предложить ему партию.
Russian Adobe Flash-platform User Group
Debug mode для FDT 3
В FDT 3 очень удобно работать, но в нем нет дебаг-режима. Способ дебагить, работая в FDT:
- Делаем две копии Eclipse:
- В одной установлен Flex Builder Plugin (далее - Flex)
- В другой - FDT 3 (далее - FDT)
- Во Flex создаем произвольный проект
- Добавляем в него папку нашего проекта через Link to the folder in file system
- Заходим в Run -> Open Debug Dialog…
- Заводим новый профиль во Flex Application
- Вкладка Main:
- Выбираем наш пустой проект, его главный файл
- URL or path to launch - убираем галочку с Use defaults и прописываем во все три поля путь к HTML-обертке нашего главного проекта. У меня это выглядит как “C:\projects\AS3_WS\Portal\bin\Main.html”
- Вкладка Source:
- Добавляем папки с кодом главного проекта (пути - абсолютные). У меня это “C:\projects\AS3_WS\Portal\src\” и “C:\projects\AS3_WS\Portal\lib\”
Все. Жмем F11, запускается наш FDT-билд с дебагом внутри Flex. Расставляем breakpoints во Flex, запускаем - работают! Стек вызовов, все дела. Ура!
