Надежные синхронизированные события в JavaScript - PullRequest
2 голосов
/ 31 декабря 2011

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

См. это демо - все, что я делаю, это "активирую" баркаждые x мсек, а некоторые блоки пропускаются.Это может быть неплохо на быстрых ПК, но как только фактическая работа будет выполнена в цикле, она определенно развалится.

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

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

Ответы [ 4 ]

2 голосов
/ 31 декабря 2011

Ваша функция be_busy фактически никогда не входит в цикл while, чтобы быть занятым в течение 100 мс.Вам нужно перевернуть < на > или >=.Так что эти пропуски просто из-за задержек в вашем браузере, которые немного отстают от времени.Но на самом деле эти задержки незначительны, если у вас есть должная занятая функция.Если вы щелкнете по знаку, вы получите следующее: http://jsfiddle.net/Paulpro/PvQFh/40/embedded/result/

Ожидается, что он будет пропускать блоки таким образом, потому что Javascript является однопоточным.Таким образом, ваш первый вызов refresh_ui произойдет через 50 мс.Ваш следующий через 100 мс.И вы должны увидеть, что первые два блока установлены активными.

Затем при 120 мс вызывается be_busy, и он удерживает поток в течение 100 мс.поэтому refresh_ui не может быть вызван (независимо от того, используете ли вы setTimeout или setInterval, этот факт не меняется) до 220 мс.refresh_ui будет вызван на 220, и вы увидите, что третий блок активен.Затем в 240 мс be_busy будет вызван снова.Поэтому следующее обновление не может происходить до 340 мс.Когда это произойдет, 5-й блок будет активирован в (340/80 = 4.x).

Пропущенный четвертый блок - ожидаемое поведение в лучших браузерах.Браузер, который не пропускает этот блок, вероятно, отстает еще больше.

Таким образом, вы должны раскрасить первые три блока, затем белый прямоугольник, затем 2 красных, затем белый и т. Д. И каждые 10 групп по двав красных блоках будет один белый блок.

Единственный реальный способ обойти это - разбить be_busy на кучу более мелких функций, которые разбивают работу на части, или использовать Web Workers для выполнения фоновой (занятой) обработки, но Web Workers будут работать только в самых последних браузерах.

1 голос
/ 31 декабря 2011

Объедините установленный интервал с вашим собственным записанным временем.

Вот пример того, как вы можете запускать что-то каждую секунду:

var startTime = new Date();
var bars = [false, false, false, false, ...] // all your bars.
var i = 0;
var intervalMs = 1000;
function doStuff() {
    var currentTime = new Date();
    if (currentTime - startTime < i * intervalMs && bar[i] != true) {
          bars[i] = true;
          i++;
    }
}

setInterval(doStuff, 10);

Убедитесь, что интервал в setInterval намного более гранулированный, чем интервал, который вы хотели бы запустить функцию.

В сторону: это распространенная проблема синхронизации при разработке игр. Многие функции рендеринга API разработчиков игр будут использовать затраченное время в качестве аргумента, чтобы помочь синхронизировать изменения на сцене. Если у вас много объектов для рендеринга и синхронизации, это хороший подход.

1010 *, например *

MyScene.prototype.render(timeMs) {
   // calculate movements and render the scene based on elapsed time.
   MyOtherObjects.render(timeMs);
   MyObjects.render(timeMs);
   // etc.
}

var scene = new MyScene();
var lastRenderTime = new Date();
function tick() {
    var time = new Date();
    scene.render(time - lastRenderTime);
    lastRenderTime = time;
}

setInterval(tick, 1000/60); // render at 60fps

Таким образом, если setInterval не синхронизирован из-за замедления процессора и других факторов, функция рендеринга может повторно синхронизировать сцену по истекшему времени.

0 голосов
/ 31 декабря 2011

setInterval () не даст вам гарантии, что функция будет вызываться во времени, она просто останавливает выполнение до истечения времени.лучше, если вы рассчитываете до времени и после времени.

0 голосов
/ 31 декабря 2011

Вместо setInterval () используйте setTimeout ().Это гораздо точнее и надежнее.

...