Привет!
Пишу впервые, немного нервничаю, поэтому уже в четвертый раз набираю первое предложение.
Я занимаюсь разработкой игр. Преимущественно на флеше. Больше года работал с HaXe NME — круто. Но много своих «выкрутасов», которые, если сравнивать с AS3 при таргете во флеш, можно уместить в отдельную статью.
За более чем два года опыта выделил одну насущную для меня проблему при создании флеш игр.
Это первичная инициализация. Проблема заключается в том, что пока пользователь смотрит на прогресс-бар прелоадера, в бэкграунде выполняется множество операций — от загрузки данных и ассетов до их подготовки. И, зачастую, количество этих операций зашкаливает даже в самом начале разработки, не говоря уже об обновлениях.
Очень много смотрел чужие исходники игр и видел ужасные нагромождения конструкций вида:
/**
* calling server (pseudo-code)
*/
private function startInitialization():void {
ServerConnection.instance.call('method', params, onCallMethodComplete);
}
private function onCallMethodComplete():void {
ServerConnection.instance.call('methodTwo', params, onCallMethodTwoComplete);
}
private function onCallMethodTwoComplete():void {
AssetsLoader.loadAssetZipByName('assetName', onAssetLoaded);
}
private function onAssetLoaded():void {
// etc
}
При этом, не всегда пользуются какими-то вспомогательными синглтон-классами в методах, и зачастую в методах класса загрузки появляется куча loader-ов для каждой операции со всеми вытекающими слушателями. Мешанина не читаемая абсолютно. Сам так же делал когда-то.
На вкус и цвет, как говорится, но когда количество методов для инициализации вырастает хотя бы до десятка — читать такой код становится трудно. А если впоследствии внезапно понадобится вставить дополнительную промежуточную функцию, скажем, для дополнительной обработки полученных данных, — это превращается в головную боль и уйму времени, потраченного на попытки разобраться что за чем идёт.
Такая проблема актуальна не только для загрузки данных. А так же, например, последовательной анимации элементов интерфейса. Кто-то возразит, что анимировать можно и во Flash IDE — спору нет. Однако, зачастую при анимации появления окна требуется не только показывать элементы по порядку, но ещё и активировать/деактивировать их, чтобы не возникало случайных закрытий окна по пользовательскому клику до завершения анимаций.
Задача вроде бы понятная и достаточно тривиальная. Вижу цель. Иду к цели.
Утилитка, которую я написал, — весьма небольшая и вполне себе простая. Однако, она очень помогла мне перейти от конструкций, приведенных выше, к вполне лаконичному:
private function init():void {
Instruction.create()
.add(configureViewTree)
.add(initializeManagers)
.add(SocialData.instance.init, flashVars)
.add(GameData.instance.init, 'http://server.ru/', 'http://static.server.ru/assets/')
.add(Assets.getZip, 'music/music.zip')
.add(MetaData.instance.init)
.add(World.instance.create, worldContainer)
.execute(startGame);
}
Все методы вызовутся в той же последовательности, в которой они добавлены.
Последовательность действий плюс относительно удобная читаемость очереди. Казалось бы, что ещё нужно?!
package {
/**
* ...
* @author Frost
*/
public class Instruction {
public static function create():Instruction {
return new Instruction();
}
private var functionsQueue:Vector.<Function>;
private var argumentsQueue:Vector.<Array>;
public function Instruction() {
functionsQueue = new Vector.<Function>();
argumentsQueue = new Vector.<Array>();
}
public function add(calledFunction:Function, ...params):Instruction {
functionsQueue.push(calledFunction);
argumentsQueue.push(params);
return this;
}
public function execute(completeHandler:Function = null, ...params):void {
add(completeHandler, params).checkQueue();
}
private function doTask():void {
var calledFunctionArguments:Array = argumentsQueue.shift();
calledFunctionArguments.unshift(checkQueue);
functionsQueue.shift().apply(null, calledFunctionArguments);
calledFunctionArguments = null;
}
private function checkQueue():void {
if (functionsQueue.length > 1) {
doTask();
return;
}
completeInstruction();
}
private function completeInstruction():void {
if (functionsQueue[0] != null) {
functionsQueue.shift().apply(argumentsQueue.shift());
}
functionsQueue = null;
argumentsQueue = null;
}
}
}
Минусов у подобной структуры несколько. Во-первых, функции, которые мы хотим запихнуть в очередь, должны соответствовать определённой структуре, а именно — первым параметром идёт колбек на завершение метода, остальные параметры — это аргументы. Положительным моментом может служить то, что колбек можно запихнуть как на загрузку данных в обработчик Event.COMPLETE, так и просто поставить его вызов последним, после проведения различных операций:
function weWantToAddToInstruction(completeHandler:Function, paramOne:Object, paramTwo:String, paramThree:Function, ...params):void {
// do some crazy stuff
completeHandler();
}
Так же, к минусу можно отнести и то, что обработка ошибок в выполняемой инструкции лежит на плечах выполняемых методов. Я пока не придумал решение, которое позволит вынести отлов ошибок под ответственность самой инструкции. В вариантах, что я пробовал — приходилось сильно менять структуру функций => не удобно. Да и во многих ситуациях всё заходило в тупик. Так-то было бы здорово.
Ещё одним, но побочным эффектом, оказалось то, что, начав использовать инструкцию, я стал часто плодить анонимные функции внутри вызываемых методов, чтобы до вызова completeHandler сработала ещё какая-нибудь функция или выставление флага, что не правильно. Но это, скорее, результат моей лени. Т.к. в таких случаях внутри метода, который вызывается внутри инструкции, — можно создать ещё одну инструкцию и упорядочить код, обойдя стороной анонимки.
Положительным аспектом стало то, что в инструкцию можно запихивать методы, аргументы для которых — абсолютно произвольные, как по содержанию, так и по количеству. Это весьма удобно. Но лишь в том случае, если твёрдо уверен, что аргументы — именно те, которые задуманы. Хотя, это правило применимо ко всему, наверное.
В общем-то это всё. Благодарю за внимание, читатели.
Очень здорово, если то, что я написал, будет кому-то полезно.
Автор: Frost47rus