Просмотр полной версии : Разбиение ресурсоёмких операций по кадрам
Думаю многим приходилось сталкиваться с тем, что какое-либо вычисление требует большой затраты времени, при этом критично чтобы не падал fps и совершенно не критично, что операция закончится в том же кадре, что и началась.
Я представляю себе реализацию такого механизма примерно так:
Регистрируем два слушателя ENTER_FRAME с приоритетами такими, чтобы первое вызывалось заведомо раньше всех остальных, а второе - заведомо позже.
В первом просто запоминаем время через getTimer();
Во втором проверяем сколько времени прошло с момента ENTER_FRAME и по-возможности запускаем обработчики, которые ведут вычисления, после их выполнения снова смотрим время и т.д.
При этом обработчики должны обладать фиксированным временем исполнения (не использовать рекурсий, пробежек по циклам, с заранее неизвестным размером и т.д.), каждый нужно протестировать на среднее время исполнения и захардкодить это время.
Может быть у кого-то есть более "прямые" идеи реализации сего?
Также меня смущает такой вопрос: как узнать какая часть 1/framerate сек расходуется на рендеринг и какое время с момента входа в кадр я могу считать за дедлайн для скрипта чтобы не просел фпс.
incvizitor
13.09.2010, 19:29
var _i:uint=0;
var _j:uint=0;
var over:Shape=new Shape();
addChild(over);
trace(getTimer());
longCicle();
function longCicle():void{
var t:uint=getTimer();
for(var i:uint=_i; i<100000; i++){
for(var j:uint=_j; j<20000; j++){
}
_j=0;
if(getTimer()-t> 20){
_i=i;
_j=j;
var p:Number = i * j / 100000 / 20000;
drawProgress(p);
setTimeout(longCicle,1);
return;
}
}
drawProgress(1);
trace(getTimer() - 1000);
}
function drawProgress(p:Number):void{
over.graphics.clear();
over.graphics.beginFill(0,0.4);
over.graphics.drawRect(0,0,stage.stageWidth * p, stage.stageHeight);
}
Что то типа этого?
Может проще счетчик fps сделать, и уже исходя из его отношения currentFps к targetFps делать какие-либо выводы?
2 incvizitor:
Да, примерно так, но в этом примере во-первых жёстко вбито выполнение не более 20 мс, тогда как эта величина явно должна зависеть от fps, с которым скомпилена swf, иначе при высоком fps он всё же будет проседать, а при низком наоборот будет наблюдаться неэффективное использование ресурсов.
Кроме того пример расчитан только на одно долгое вычисление, а их может быть несколько разного типа и начинающихся по разным событиям.
2 i.o.:
Ну вообще идея - достичь такого, чтобы currentFps тождественно равнялся targetFps.
Я на самом деле надеялся что кто-то предложит какой-нибудь стандартный класс (т.е. де-факто стандартный, а не входящий в стандартный набор), которым все профи активно пользуются и который я не смог найти через поиск, так как не подобрал подходящих ключевых слов.
Вот здесь как раз тот редкий случай когда удобно использовать конвеер Потапенко (ну или самому упрощенную версию слепить),
но придется задать число шагов фиксированным до начала вычислений
for (var i:int = 0; i < NUM_STEPS; i++)
{
conveyor.add(doStep, i);
conveyor.addFrames(1);// Добавляем ожидание следующего кадра
}
// Добавляем вычисления другого рода
for (var i:int = 0; i < NUM_ANOTHER_STEPS; i++)
{
conveyor.add(...);
conveyor.addFrames(1);// Добавляем ожидание следующего кадра
}
conveyor.add(onComplete);// Добавляем колбек завершения всех вычислений
conveyor.play();
private function doStep(step:int):void
{
// считаем шаг step
}
Иначе решение incvizitor'а самое эффективное, хоть и не особо читаемое
Существует еще такой способ (о надежности сложно говорить, но, по идее порядок операций поломаться не должен):
const DELAY:int = 10;
for (var i:int = 0; i < NUM_STEPS; i++)
{
setTimeout(doStep, i * DELAY, i);
}
setTimeout(onComplete, NUM_STEPS * DELAY);
2 expl:
Отлично, ещё бы где-нибудь этот конвейер найти, так как все ссылки в поиске по этому форуму, да и просто в поисковике ведут на potapenko.com, где сейчас нет ничего, кроме предложения стать обладателем этого домена.
totto
а чем не подходит задавать кол-во времени на выполнение исходя из текущего фпс?
incvizitor
14.09.2010, 15:27
@ i.o.:
Наверное тем что Вы не объяснили нам как это сделать.
Чего тут объяснять?
Есть счетчик фпс, высчитывающий currentFps.
Есть исходное фпс к которому мы стремимся - targetFps.
Каждый раз, перед тем как запустить числодробильные операции - мы считаем отношение fpsRatio = currentFps / targetFps.
Если fpsRatio меньше единицы, то у нас идет перегрузка процессора. Вот от этой величины и пляшем - умножаем ее на расчетное время, при котором у нас фпс не провисает, и получаем время для исполнения числодробильных операций, при котором они не просадят фпс.
incvizitor
14.09.2010, 15:42
Ок. Я просто подумал что Вы предлагали в самом цикле проверять ФПС, поэтому немного удивился :)
ничего не понял...
Умножаем на время, при котором не просядет фпс, и получаем время, при котором не просядет фпс. Это как?
Может имелось в виду что надо задать какое-то разумное время, а затем в каждом кадре если fpsRatio < 1, то время уменьшать, а если fpsRatio > 1, то увеличивать.
Только > 1 быть никак не может, так как targetFps вкомпиливается в swf и чаще рендер производиться не будет. Так что в итоге операции будут работать по пиковой загрузке (обеспеченной не только флешом, но и другими программами). Хотя конечно можно и при fpsRatio = 1 увеличивать... Тогда флешка по идее будет всегда работать при слегка присевшем фпс, но зато не будет больших проседаний.
Умножаем на время, при котором не просядет фпс, и получаем время, при котором не просядет фпс. Это как?
Прочитайте внимательнее.
Объясняю последний раз:
В самом начале у нас есть тормозная функция megaCalculation( timeLimit:uint ), которая принимает время в мс, которое она должна отпахать, и не более.
В самом начале у нас есть targetFps = 30
В самом начале у нас есть расчетное_время, скажем 33мс на кадр (из расчета 1000мс / 30фпс)
У нас есть счетчик фпс
Каждый кадр мы узнаем отношение fpsRatio как currentFps / targetFps
И после этого вызываем мегаТормозную функцию как megaCalculation( fpsRatio * расчетное_время )
расчетное_время - "Умножаем на время, при котором не просядет фпс"
fpsRatio - "Умножаем"
fpsRatio * расчетное_время - "и получаем время, при котором не просядет фпс"
Еще вопросы есть?
gloomyBrain
14.09.2010, 17:54
@totto
Есть такое событие Event.EXIT_FRAME - так что длительность текущего кадра можно считать по нему. Необходимую длительность кадра можно записать в констатнту. Соответственно, сравнить будет просто.
Далее, в обработчике ENTER_FRAME запоминаем, сколько собираемся сделать операций. Делаем операции. В EXIT_FRAME смотрим, не просел ли fps. Если просел - уменьшаем число операций для следующего ENTER_FRAME
gloomyBrain, тот же самый счетчик фпс получается. Только с настоящим точнее, ибо он захватывает и время, за которое полностью рендерится кадр. А в твоем случае мы сможем узнать только время работы скриптов.
gloomyBrain
14.09.2010, 18:00
Да я больше к тому, что как-то странно это - регистрировать несколько ENTER_FRAME с разными приоритетами
Да я больше к тому, что как-то странно это - регистрировать несколько ENTER_FRAME с разными приоритетами
ну это да, правильно )
i.o.
Спасибо, теперь наконец-то понятно что Вы имели в виду.
Я думаю, что воспользуюсь таким подходом. Хотя мне кажется ориентироваться на жёстко задаваемую константу всё же не стоит, а лучше изменять это самое расчётное время в зависимости от проседания/не проседания fps.
В общем ещё раз спасибо.
gloomyBrain
EXIT_FRAME срабатывает сразу после ENTER_FRAME и до RENDER, так что разница между подпиской на ENTER_FRAME с низшим приоритетом и подпиской на EXIT_FRAME с практической точки зрения нет. Только разве что с точки зрения красоты кода.
Хотя мне кажется ориентироваться на жёстко задаваемую константу всё же не стоит, а лучше изменять это самое расчётное время в зависимости от проседания/не проседания fps.
А вот это не советую делать. Это уже выполняется с помощью fpsRatio * расчетное_время.
Единственное что могу подсказать, так это высчитывать при запуске приложения максимально приемлимое время выполнения, при котором фпс чуть не проседает. Т.е высчитывать в самом начале расчетное_время. Потому что у всех компы разные, и если даже скрипт будет выполнятся ровно столько времени сколько ему отведете, то графика может не успевать отрисовываться за оставшееся время.
Поэтому советую запускать что-то вроде теста производительности на старте.
Я имел в виду, что вместо вызова
megaCalculation(fpsRatio*time);
где time - это и есть то самое расчётное время
буду делать как-то так:
if (fpsRatio < FPS_RATIO_MIN)
time -= 1;
else if (fpsRatio > FPS_RATIO_MAX)
time += 1;
megaCalculation(time);
Чем это плохо-то? Вообще конечно есть смысл реализовать оба варианта и протестировать.
Время на отрисовку графики зависит не только от производительности компа, но и от того сколько нужно перерисовать и сколько DisplayObject добавлено на сцену, поэтому как мне думается, тест производительности в самом начале может совершенно не отражать дальнейшей ситуации со скоростью отрисовки.
2 expl:
Отлично, ещё бы где-нибудь этот конвейер найти, так как все ссылки в поиске по этому форуму, да и просто в поисковике ведут на potapenko.com, где сейчас нет ничего, кроме предложения стать обладателем этого домена.
Вот это да :eek: Сайт Потапенко снесен, а в сети нет больше кода этого конвеера,
хотя что-то похожее народ делает (http://sheremetov.com/flash/flexunit-async/)
Забейте тогда, чтож поделаешь.
Работает на vBulletin ® версия 3.7.3. Copyright ©2000-2026, Jelsoft Enterprises Ltd. Перевод: zCarot
Copyright © 1999-2008 Flasher.ru. All rights reserved.