PDA

Просмотр полной версии : Разбиение ресурсоёмких операций по кадрам


totto
13.09.2010, 19:16
Думаю многим приходилось сталкиваться с тем, что какое-либо вычисление требует большой затраты времени, при этом критично чтобы не падал 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);
}



Что то типа этого?

i.o.
13.09.2010, 19:33
Может проще счетчик fps сделать, и уже исходя из его отношения currentFps к targetFps делать какие-либо выводы?

totto
13.09.2010, 21:37
2 incvizitor:
Да, примерно так, но в этом примере во-первых жёстко вбито выполнение не более 20 мс, тогда как эта величина явно должна зависеть от fps, с которым скомпилена swf, иначе при высоком fps он всё же будет проседать, а при низком наоборот будет наблюдаться неэффективное использование ресурсов.
Кроме того пример расчитан только на одно долгое вычисление, а их может быть несколько разного типа и начинающихся по разным событиям.
2 i.o.:
Ну вообще идея - достичь такого, чтобы currentFps тождественно равнялся targetFps.

Я на самом деле надеялся что кто-то предложит какой-нибудь стандартный класс (т.е. де-факто стандартный, а не входящий в стандартный набор), которым все профи активно пользуются и который я не смог найти через поиск, так как не подобрал подходящих ключевых слов.

expl
13.09.2010, 21:48
Вот здесь как раз тот редкий случай когда удобно использовать конвеер Потапенко (ну или самому упрощенную версию слепить),
но придется задать число шагов фиксированным до начала вычислений


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);

totto
14.09.2010, 00:08
2 expl:
Отлично, ещё бы где-нибудь этот конвейер найти, так как все ссылки в поиске по этому форуму, да и просто в поисковике ведут на potapenko.com, где сейчас нет ничего, кроме предложения стать обладателем этого домена.

i.o.
14.09.2010, 10:12
totto
а чем не подходит задавать кол-во времени на выполнение исходя из текущего фпс?

incvizitor
14.09.2010, 15:27
@ i.o.:
Наверное тем что Вы не объяснили нам как это сделать.

i.o.
14.09.2010, 15:38
Чего тут объяснять?
Есть счетчик фпс, высчитывающий currentFps.
Есть исходное фпс к которому мы стремимся - targetFps.
Каждый раз, перед тем как запустить числодробильные операции - мы считаем отношение fpsRatio = currentFps / targetFps.
Если fpsRatio меньше единицы, то у нас идет перегрузка процессора. Вот от этой величины и пляшем - умножаем ее на расчетное время, при котором у нас фпс не провисает, и получаем время для исполнения числодробильных операций, при котором они не просадят фпс.

incvizitor
14.09.2010, 15:42
Ок. Я просто подумал что Вы предлагали в самом цикле проверять ФПС, поэтому немного удивился :)

totto
14.09.2010, 17:39
ничего не понял...
Умножаем на время, при котором не просядет фпс, и получаем время, при котором не просядет фпс. Это как?
Может имелось в виду что надо задать какое-то разумное время, а затем в каждом кадре если fpsRatio < 1, то время уменьшать, а если fpsRatio > 1, то увеличивать.
Только > 1 быть никак не может, так как targetFps вкомпиливается в swf и чаще рендер производиться не будет. Так что в итоге операции будут работать по пиковой загрузке (обеспеченной не только флешом, но и другими программами). Хотя конечно можно и при fpsRatio = 1 увеличивать... Тогда флешка по идее будет всегда работать при слегка присевшем фпс, но зато не будет больших проседаний.

i.o.
14.09.2010, 17:43
Умножаем на время, при котором не просядет фпс, и получаем время, при котором не просядет фпс. Это как?
Прочитайте внимательнее.

Объясняю последний раз:
В самом начале у нас есть тормозная функция 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

i.o.
14.09.2010, 17:57
gloomyBrain, тот же самый счетчик фпс получается. Только с настоящим точнее, ибо он захватывает и время, за которое полностью рендерится кадр. А в твоем случае мы сможем узнать только время работы скриптов.

gloomyBrain
14.09.2010, 18:00
Да я больше к тому, что как-то странно это - регистрировать несколько ENTER_FRAME с разными приоритетами

i.o.
14.09.2010, 18:02
Да я больше к тому, что как-то странно это - регистрировать несколько ENTER_FRAME с разными приоритетами
ну это да, правильно )

totto
14.09.2010, 20:04
i.o.
Спасибо, теперь наконец-то понятно что Вы имели в виду.
Я думаю, что воспользуюсь таким подходом. Хотя мне кажется ориентироваться на жёстко задаваемую константу всё же не стоит, а лучше изменять это самое расчётное время в зависимости от проседания/не проседания fps.
В общем ещё раз спасибо.
gloomyBrain
EXIT_FRAME срабатывает сразу после ENTER_FRAME и до RENDER, так что разница между подпиской на ENTER_FRAME с низшим приоритетом и подпиской на EXIT_FRAME с практической точки зрения нет. Только разве что с точки зрения красоты кода.

i.o.
14.09.2010, 20:19
Хотя мне кажется ориентироваться на жёстко задаваемую константу всё же не стоит, а лучше изменять это самое расчётное время в зависимости от проседания/не проседания fps.
А вот это не советую делать. Это уже выполняется с помощью fpsRatio * расчетное_время.
Единственное что могу подсказать, так это высчитывать при запуске приложения максимально приемлимое время выполнения, при котором фпс чуть не проседает. Т.е высчитывать в самом начале расчетное_время. Потому что у всех компы разные, и если даже скрипт будет выполнятся ровно столько времени сколько ему отведете, то графика может не успевать отрисовываться за оставшееся время.
Поэтому советую запускать что-то вроде теста производительности на старте.

totto
14.09.2010, 20:46
Я имел в виду, что вместо вызова

megaCalculation(fpsRatio*time);

где time - это и есть то самое расчётное время
буду делать как-то так:

if (fpsRatio < FPS_RATIO_MIN)
time -= 1;
else if (fpsRatio > FPS_RATIO_MAX)
time += 1;
megaCalculation(time);

Чем это плохо-то? Вообще конечно есть смысл реализовать оба варианта и протестировать.
Время на отрисовку графики зависит не только от производительности компа, но и от того сколько нужно перерисовать и сколько DisplayObject добавлено на сцену, поэтому как мне думается, тест производительности в самом начале может совершенно не отражать дальнейшей ситуации со скоростью отрисовки.

expl
14.09.2010, 22:21
2 expl:
Отлично, ещё бы где-нибудь этот конвейер найти, так как все ссылки в поиске по этому форуму, да и просто в поисковике ведут на potapenko.com, где сейчас нет ничего, кроме предложения стать обладателем этого домена.

Вот это да :eek: Сайт Потапенко снесен, а в сети нет больше кода этого конвеера,
хотя что-то похожее народ делает (http://sheremetov.com/flash/flexunit-async/)
Забейте тогда, чтож поделаешь.