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

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

Рейтинг: 5.00. Голосов: 4.

Разгружаем ресурсоемкие циклы на мобилах без воркеров

Запись от caseyryan размещена 13.07.2015 в 17:35
Обновил(-а) caseyryan 13.07.2015 в 21:24

Что-то совсем у меня записей в блогах нет, как-то даже не модно)
Пусть хоть какая-то мелочь будет, тем более, что повод как раз появился

И так, ближе к делу. Тем, кто занимается разработками на AIR под мобильные платформы, известно на сколько критична там бывает производительность. Если многие проблемы с графикой сейчас успешно решает Stage3D, то проблемы с созданием объектов в одном потоке никуда не делись. Использование Worker'ов не всегда может решить проблему нагрузок, тем более, что на iOS они не работают в принципе. Да и API у них реализован через то самое место. В общем, воркеры - не вариант.
Вчера добавил в свою новую игру спецэффектов на частицах, в виде дождя с подвижными облаками грозами и молниями и обнаружил одну неприятную вещь. При запуске на слабом устройстве (конкретно Samsung Galaxy S-7270 Ace 3), при создании всего этого добра + остальных объектов уровня в цикле, игра на какое-то время подвисает, система думает, что она зависла совсем, и убивает процесс. Естественно, вылет игры не понравится ни мне, как разработчику, ни игрокам. Проверил все это дело в скауте, и мои догадки подтвердились. Дело именно в том, что создание объектов в циклах при старте уровня и есть "корень зла", этот код занимает больше всего времени. Вылетает через раз, но если не вылетела, то FPS поднимается до 60 и все без проблем работает вместе со всеми эффектами. Стало быть надо как-то разгрузить циклы. Распределить цикл по нескольким кадрам невозможно, поэтому решил написать специальную "тулзу", в которой можно заменить циклы и растянуть выполнение этого кода на несколько кадров.
Вот, собственно, этот класс

Код AS3:
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() {
 
	}
}
Тут все довольно просто. Пример работы:

Код AS3:
// как выглядит этот код в простом цикле
for (var i:uint = 0; i < 10; i++) {
	trace("Итетарция в простом цикле", i);
}
 
// а так в растянутом на несколько кадров
new TimerLoop().createLoop(10, function(i:uint):void {
	trace("Итетарция в растянутом цикле", i);
});
Как видно из примера, место эта конструкция занимает практически столько же. Но позволяет увеличить время на выполнение некоторых операций, не заставляя всю среду зависать. Во время выполнения таких "циклов", можно проигрывать анимации, и вообще делать все, что угодно.
Если нужно увеличить интервалы, просто передаем их в параметр delay

Так же можно организовать очередь
Код AS3:
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();
Конечно минусом этой конструкции является то, что времени на полное завершение всех операций требуется больше, чем при использовании простых циклов. Но есть места, где гораздо важнее стабильность (как в моем примере)


И вот еще версия для старлинга (без таймера)
Код AS3:
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 20:20 ZackMercury вне форума
ZackMercury
 
Аватар для ZackMercury
Во, полезная вещь.
Ещё приходила идея сделать "растянутую" генерацию уровня, чтобы каждое действие демонстрировалось в течении некоторого времени, когда можно прочитать текст статуса, и в статус писалось имя выполнения текущей операции.
А реализовать это наподобии "стека".
Код AS3:
stack.push("Generating Landscape", function(){...});
Старый 13.07.2015 21:21 caseyryan вне форума
caseyryan
 
Аватар для caseyryan
Ну это тот же принцип, по идее. Тут можно малость допилить, и выводить как раз названия
Старый 13.07.2015 21:30 ZackMercury вне форума
ZackMercury
 
Аватар для ZackMercury
За стеками будущее.)
Кстати, неплохо так удобно формировать блоки кода с помощью стека, визуально выглядит крутяк, дак ещё потом какие возможности по управлению открываются - меняй порядок, ставь на паузу, задерживай, и вообще что хочешь делай.
Старый 13.07.2015 22:33 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Слушайте, если тормозят конструкторы, то можно вынести весь код из них в отдельный метод, как-то так:
Код AS3:
public function MyClass()
{
    init();
}
 
private function init():void
{
    // бла-бла-бла
}
Насколько я помню, код в конструкторах не JIT'ится, и из-за этого работает медленнее чем любые другие методы.
Сам не проверял на девайсе, но думаю что прием может оказаться полезным
Старый 14.07.2015 09:48 caseyryan вне форума
caseyryan
 
Аватар для caseyryan
Возможно. Но у меня нет кода в конструкторах. Тормозят циклы в другом месте
Старый 14.07.2015 10:11 Astraport вне форума
Astraport
 
Аватар для Astraport
Интересно, твой метод не поможет в моей проблеме? http://www.flasher.ru/forum/showthread.php?p=1184927
Старый 14.07.2015 10:38 caseyryan вне форума
caseyryan
 
Аватар для caseyryan
Без понятия, мне бы свою проблему решить http://www.flasher.ru/forum/showthread.php?t=211219 )
Старый 09.12.2015 21:26 dimarik вне форума
dimarik
 
Аватар для dimarik
похоже на еще один способ переноса синхрона на асинхрон.
http://www.senocular.com/flash/tutor...ations/?page=1
 

 


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


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