Разгружаем ресурсоемкие циклы на мобилах без воркеров
Что-то совсем у меня записей в блогах нет, как-то даже не модно)
Пусть хоть какая-то мелочь будет, тем более, что повод как раз появился
И так, ближе к делу. Тем, кто занимается разработками на AIR под мобильные платформы, известно на сколько критична там бывает производительность. Если многие проблемы с графикой сейчас успешно решает Stage3D, то проблемы с созданием объектов в одном потоке никуда не делись. Использование Worker'ов не всегда может решить проблему нагрузок, тем более, что на iOS они не работают в принципе. Да и API у них реализован через то самое место. В общем, воркеры - не вариант.
Вчера добавил в свою новую игру спецэффектов на частицах, в виде дождя с подвижными облаками грозами и молниями и обнаружил одну неприятную вещь. При запуске на слабом устройстве (конкретно Samsung Galaxy S-7270 Ace 3), при создании всего этого добра + остальных объектов уровня в цикле, игра на какое-то время подвисает, система думает, что она зависла совсем, и убивает процесс. Естественно, вылет игры не понравится ни мне, как разработчику, ни игрокам. Проверил все это дело в скауте, и мои догадки подтвердились. Дело именно в том, что создание объектов в циклах при старте уровня и есть "корень зла", этот код занимает больше всего времени. Вылетает через раз, но если не вылетела, то FPS поднимается до 60 и все без проблем работает вместе со всеми эффектами. Стало быть надо как-то разгрузить циклы. Распределить цикл по нескольким кадрам невозможно, поэтому решил написать специальную "тулзу", в которой можно заменить циклы и растянуть выполнение этого кода на несколько кадров.
Вот, собственно, этот класс
package { import flash.events.TimerEvent; import flash.utils.Timer; /** * Класс помощник для замены циклов в тех местах, * где нужно равномернее распределить нагрузку * и не допустить зависания приложения при выполнении * увесистых операций в цикле * * @author Casey Ryan */ public class TimerLoop { private var _timer: Timer = null; private var _iterator: uint = 0; private var _closure: Function = null; private var _onComplete: Function = null; private var _bunch: Array/*TimerLoopBunchItem*/ = []; private const _delay: Number = 20.0; public function TimerLoop() { } /** * Добавляет в очередь функцию, которую нужно выполнить определенное количество раз * @param repeatCount необходимое количество итераций * @param closure функция, которая будет вызываться при каждой итерации. Принимает один * аргумент типа uint с номером текущей итерации * @param delay временной интервал между итерациями в миллисекундах */ public function addToBunch(repeatCount:uint, closure:Function, delay:Number = NaN):void { var timerLoopBunchItem:TimerLoopBunchItem = new TimerLoopBunchItem(); timerLoopBunchItem.closure = closure; timerLoopBunchItem.delay = isNaN(delay) ? _delay : delay; timerLoopBunchItem.repeatCount = repeatCount; _bunch.push(timerLoopBunchItem); } public function executeBunch():void { if (_bunch.length < 1) { return; } var timerLoopBunchItem:TimerLoopBunchItem = _bunch[0]; _iterator = 0; _closure = timerLoopBunchItem.closure; if (!_timer) { var delay:Number = isNaN(timerLoopBunchItem.delay) ? _delay : timerLoopBunchItem.delay; _timer = new Timer(delay, timerLoopBunchItem.repeatCount); _timer.addEventListener(TimerEvent.TIMER, onTimer); _timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete); } else { _timer.reset(); _timer.delay = timerLoopBunchItem.delay; _timer.repeatCount = timerLoopBunchItem.repeatCount; } _timer.start(); _bunch.shift(); } private function onTimer(e:TimerEvent):void { if (_closure == null) dispose(); _closure.call(null, _iterator); _iterator ++; } private function onTimerComplete(e:TimerEvent):void { if (_bunch.length < 1) { if (_onComplete != null) { _onComplete(); } } else { executeBunch(); } } /** * Начинает цикличное выполнение функции, через заданные интервалы времени * @param repeatCount необходимое количество итераций * @param closure функция, которая будет вызываться при каждой итерации. Принимает один * аргумент типа uint с номером текущей итерации * @param delay временной интервал между итерациями в миллисекундах * @param onComplete функция вызываемая по завершении всех итераций */ public function createLoop(repeatCount:uint, closure:Function, delay:Number = NaN, onComplete:Function = null):void { if (_bunch.length > 0) throw new ArgumentError("нельзя выполнить отдельный цикл, пока не выполнилась очередь"); delay = isNaN(delay) ? _delay : delay; _iterator = 0; _closure = closure; _onComplete = onComplete; if (!_timer) { _timer = new Timer(delay, repeatCount); _timer.addEventListener(TimerEvent.TIMER, onTimer); _timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete); } else { _timer.reset(); _timer.delay = delay; _timer.repeatCount = repeatCount; } _timer.start(); } public function reset():void { if (_timer) { _timer.stop(); } _iterator = 0; _closure = null; } public function dispose():void { if (_timer) { _timer.stop(); _timer.removeEventListener(TimerEvent.TIMER, onTimer); _timer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete); } _bunch = null; _onComplete = null; _closure = null; } public function get onComplete():Function { return _onComplete; } public function set onComplete(value:Function):void { _onComplete = value; } } } internal class TimerLoopBunchItem { public var closure: Function = null; public var delay: Number = NaN; public var repeatCount: uint = 0; public function TimerLoopBunchItem() { } }
// как выглядит этот код в простом цикле for (var i:uint = 0; i < 10; i++) { trace("Итетарция в простом цикле", i); } // а так в растянутом на несколько кадров new TimerLoop().createLoop(10, function(i:uint):void { trace("Итетарция в растянутом цикле", i); });
Если нужно увеличить интервалы, просто передаем их в параметр delay
Так же можно организовать очередь
var timerLoop:TimerLoop = new TimerLoop(); timerLoop.onComplete = function():void { trace("очередь выполнена"); } timerLoop.addToBunch(10, function(i:uint):void { trace("первый в очереди", i) }); timerLoop.addToBunch(5, function(i:uint):void { trace("второй в очереди", i) } ); timerLoop.executeBunch();
И вот еще версия для старлинга (без таймера)
package { import flash.utils.getTimer; import starling.animation.IAnimatable; import starling.core.Starling; /** * Класс помощник для замены циклов в тех местах, * где нужно равномернее распределить нагрузку * и не допустить зависания приложения при выполнении * увесистых операций в цикле * * @author Casey Ryan */ public class TimerLoop implements IAnimatable { private var _iterator: uint = 0; private var _maxIterations: uint = 0; private var _closure: Function = null; private var _onComplete: Function = null; private var _bunch: Array/*TimerLoopBunchItem*/ = []; private var _delay: Number = 33.0; private var _lastTime: int = 0; public function TimerLoop() { } /** * Добавляет в очередь функцию, которую нужно выполнить определенное количество раз * @param repeatCount необходимое количество итераций * @param closure функция, которая будет вызываться при каждой итерации. Принимает один * аргумент типа uint с номером текущей итерации * @param delay временной интервал между итерациями в миллисекундах */ public function addToBunch(repeatCount:uint, closure:Function, delay:Number = NaN):void { var timerLoopBunchItem:TimerLoopBunchItem = new TimerLoopBunchItem(); timerLoopBunchItem.closure = closure; timerLoopBunchItem.delay = isNaN(delay) ? _delay : delay; timerLoopBunchItem.repeatCount = repeatCount; _bunch.push(timerLoopBunchItem); } public function advanceTime(time:Number):void { var curTime:int = getTimer(); if (curTime >= _lastTime) { if (_iterator < _maxIterations) { onTimer(); _lastTime = curTime + _delay; } else { onTimerComplete(); } } } public function executeBunch():void { if (_bunch.length < 1) { return; } var timerLoopBunchItem:TimerLoopBunchItem = _bunch[0]; _iterator = 0; _closure = timerLoopBunchItem.closure; _maxIterations = timerLoopBunchItem.repeatCount; Starling.juggler.add(this); _bunch.shift(); } private function onTimer():void { if (_closure == null) dispose(); _closure.call(null, _iterator); _iterator ++; } private function onTimerComplete():void { if (_bunch.length < 1) { if (_onComplete != null) { _onComplete(); } Starling.juggler.remove(this); } else { executeBunch(); } } /** * Начинает цикличное выполнение функции, через заданные интервалы времени * @param repeatCount необходимое количество итераций * @param closure функция, которая будет вызываться при каждой итерации. Принимает один * аргумент типа uint с номером текущей итерации * @param delay временной интервал между итерациями в миллисекундах * @param onComplete функция вызываемая по завершении всех итераций */ public function createLoop(repeatCount:uint, closure:Function, delay:Number = NaN, onComplete:Function = null):void { if (_bunch.length > 0) throw new ArgumentError("нельзя выполнить отдельный цикл, пока не выполнилась очередь"); delay = isNaN(delay) ? _delay : delay; _maxIterations = repeatCount; _iterator = 0; _closure = closure; _onComplete = onComplete; Starling.juggler.add(this); } public function reset():void { Starling.juggler.remove(this); _maxIterations = 0; _iterator = 0; _closure = null; } public function dispose():void { Starling.juggler.remove(this); _bunch = null; _onComplete = null; _closure = null; } public function get onComplete():Function { return _onComplete; } public function set onComplete(value:Function):void { _onComplete = value; } } } internal class TimerLoopBunchItem { public var closure: Function = null; public var delay: Number = NaN; public var repeatCount: uint = 0; public function TimerLoopBunchItem() {} }
Кому надо, пользуйтесь
Всего комментариев 8
Комментарии
13.07.2015 21:21 | |
Ну это тот же принцип, по идее. Тут можно малость допилить, и выводить как раз названия
|
14.07.2015 09:48 | |
Возможно. Но у меня нет кода в конструкторах. Тормозят циклы в другом месте
|
14.07.2015 10:11 | |
Интересно, твой метод не поможет в моей проблеме? http://www.flasher.ru/forum/showthread.php?p=1184927
|
14.07.2015 10:38 | |
Без понятия, мне бы свою проблему решить http://www.flasher.ru/forum/showthread.php?t=211219 )
|
09.12.2015 21:26 | |
похоже на еще один способ переноса синхрона на асинхрон.
http://www.senocular.com/flash/tutor...ations/?page=1 |
Последние записи от caseyryan
- Небольшой хак для записи или удаления файлов из директории приложения (07.04.2017)
- Как я рекламу в игру внедрял (07.02.2016)
- Самое слабое место Dragon Bones - класс TransformUtil (02.02.2016)
- Разгружаем ресурсоемкие циклы на мобилах без воркеров (13.07.2015)