setTimeout неточност #2

Отново е неделя, ден за почивка, и аз отново ще ви занимавам с ECMAScript. Преди време бях писал по въпроса, но не предполагах, че проблема може да се окаже и по-сериозен. Става въпрос за следния тест:

function testInterval()
{
	var interval = 20;
	
	var timesRepeat = 500;
	
	var count = 0;
	
	function tick()
	{
		count ++;
	}
	
	tick(); // setInterval does not do an immediate call;
	var ip = setInterval(tick, interval);
	
	var startDate = (new Date()).getTime();
	
	// JavaScript/ActionScript switch;
	var out = alert || trace;
	
	var test = function ()
	{
		clearInterval(ip);
	
		var ranfor = \"ran for \" + ((new Date()).getTime() - startDate) + \" milliseconds. \";
	
		var testResult = \"tick executed \" + count + \" times; expected \" + timesRepeat;
	
		out(ranfor + testResult);
	}
	
	setTimeout(test, timesRepeat * interval);
}
	
testInterval();

Изглежда сравнително верен, макар и пълен с лоши практики. Сигурно и тече.

IE, Opera, и flash плеъра пропадат доста гнусно -

ran for 10000 milliseconds. tick executed 321 times; expected 500

ran for 10016 milliseconds. tick executed 321 times; expected 500

ran for 10031 milliseconds. tick executed 419 times; expected 500

Mozilla Firefox се справя, но ако смъкнеш интервала на 10, проблемите почват да излизат и там.

Мога да се сетя няколко сценария, в които това може да те издъни; синхронизация на анимация със звук например. Добрата новина е, че си имам тест, спрямо който евентуално ще измисля нещо.

Ето и какво измислих -

function intervalTicker (interval, callback)
{
	this.interval = interval;
	this.callback = callback;
}			
	
intervalTicker.prototype.getTime = function ()
{
	return (new Date()).getTime();
}
	
intervalTicker.prototype.start = function ()
{
	this.left = 0;
	
	this.moment = this.getTime();
	
	var instance = this;
	
	var closure = function ()
	{
		instance.onTick();
	}
	
	this.ip = setInterval(closure, 1);
}
	
intervalTicker.prototype.onTick = function ()
{
	var now = this.getTime();
	
	this.left += now - this.moment;
	this.moment = now;
	
	while (this.left > this.interval)
	{
		this.callback.call(null);
		this.left -= this.interval;
	}
}
	
intervalTicker.prototype.stop = function ()
{
	clearInterval(this.ip);
}

Това е кода, Eто го и теста:

function testTicker()
{
	var interval = 20;
	
	var timesRepeat = 500;
	
	var count = 0;
	
	function tick()
	{
		count ++;
	}
	
	tick(); // setInterval does not do an immediate call;
	var ticker = new intervalTicker(interval, tick);
	ticker.start();
	
	var startDate = (new Date()).getTime();
	
	// JavaScript/ActionScript switch;
	var out = alert || trace;
	
	var test = function ()
	{
		ticker.stop();
	
		var runfor = \"ran for \" + ((new Date()).getTime() - startDate) + \" milliseconds. \";
	
		var testResult = \"tick executed \" + count + \" times; expected \" + timesRepeat;
	
		out(runfor + testResult);
	}
	
	setTimeout(test, timesRepeat * interval);
}
	
testTicker();

Изглежда културно, и поне го изпълнява очакваното количество пъти.

3 Коментара по “setTimeout неточност #2”

  1. Илия Горанов:

    Ама аз си мисля, че отклонението не идва от грешка в таймера, а от времето за обработка на останалата част от цикъла. Както биха казали физиците - нямаш “идеални физически условия” в експеримента и затова получаваш отклонение. Мисълта ми е, че няма как да го корегираш, защото вероятно зависи от хардуера, а не от софтуера! Пробва ли това да го завъртиш в един и същ браузър на 300Mhz и 64 РАМ и на 3Ghz и 2G РАМ? Мисля, че пак ще получиш различни резултати!?

  2. Илия Горанов:

    Иначе всички знаем как ECMA дели цяло четно на цяло четно и получава дробно нечетно :)

    Това може да те издъни не по-малко… особено ако разчиташ на float числа, а не на цели.

  3. Петьо:

    Не разполагам с гореупоменатата конфигурация - но може и от това да е. инче в цикъла нищо няма ве :) . Само едно инкрементиране на променлива?

Коментирай