JavaScript计时器内存泄漏

3
我一直在开发一个画布游戏,遇到了一些内存泄漏的问题。我以为问题与渲染和删除实体有关,但我在不渲染任何内容的情况下运行代码,发现音频调度对象本身就会导致泄漏。这个问题会导致音频在一段时间后开始发出噼啪声并停止播放。游戏仍然可以渲染,但音频停止播放——我还注意到角色的射击速度变得更慢(射击时间与调度器函数中的音符一起进行调度)。
游戏链接:Link to game 我用来处理音频的代码来自于A Tale of Two Clocks' tutorial。当我在Chrome上运行节拍器的代码并进行时间轴记录时,堆分配会增加。 为什么这段代码会导致内存泄漏?在我看来它很好。计时器ID设置为空值? 图1:节拍器本身的堆时间轴 http://i.imgur.com/IuYIcxj.png 图2:我的应用程序仅运行节拍器函数的时间轴(没有游戏实体) http://i.imgur.com/vGCbI1H.png 图3:我的应用程序正常运行 http://i.imgur.com/WbrocOH.png 这是代码:
function init(){
  var container = document.createElement( 'div' );
  // CREATE CANVAS ... ...
  audioContext = new AudioContext();
  requestAnimFrame(draw);    

  timerWorker = new Worker("js/metronomeworker.js");
  timerWorker.onmessage = function(e) {
    if (e.data == "tick") {
      // console.log("tick!");
      scheduler();
    }
    else {
      console.log("message: " + e.data);
    }
  };
  timerWorker.postMessage({"interval":lookahead});
}

function nextNote() {
  // Advance current note and time by a 16th note...
  var secondsPerBeat = 60.0 / tempo;    // Notice this picks up the CURRENT 
                                        // tempo value to calculate beat length.
  nextNoteTime += 0.25 * secondsPerBeat;    // Add beat length to last beat time

  current16thNote++;    // Advance the beat number, wrap to zero
  if (current16thNote == 16) {
      current16thNote = 0;
  }
}

function scheduleNote( beatNumber, time ) {
  // push the note on the queue, even if we're not playing.
  notesInQueue.push( { note: beatNumber, time: time } );

  if ( (noteResolution==1) && (beatNumber%2))
    return; // we're not playing non-8th 16th notes
  if ( (noteResolution==2) && (beatNumber%4))
    return; // we're not playing non-quarter 8th notes

  // create an oscillator    //   create sample
  var osc = audioContext.createOscillator();
  osc.connect( audioContext.destination );
  if (beatNumber % 16 === 0)    // beat 0 == low pitch
    osc.frequency.value = 880.0;
  else if (beatNumber % 4 === 0 )    // quarter notes = medium pitch
    osc.frequency.value = 440.0;
  else                        // other 16th notes = high pitch
    osc.frequency.value = 220.0;

  osc.start( time );              //sound.play(time)
  osc.stop( time + noteLength );  //   "      "
}

function scheduler() {
  // while there are notes that will need to play before the next      interval, 
  // schedule them and advance the pointer.
  while (nextNoteTime < audioContext.currentTime + scheduleAheadTime ) {
    scheduleNote( current16thNote, nextNoteTime );
    nextNote();
  }
}

function play() {
isPlaying = !isPlaying;

if (isPlaying) { // start playing
    current16thNote = 0;
    nextNoteTime = audioContext.currentTime;
    timerWorker.postMessage("start");
    return "stop";
} else {
    timerWorker.postMessage("stop");
    return "play";
}
}

Metronome.js:

var timerID=null;
var interval=100;

self.onmessage=function(e){
  if (e.data=="start") {
    console.log("starting");
    timerID=setInterval(function(){postMessage("tick");},interval)
  }
  else if (e.data.interval) {
    console.log("setting interval");
    interval=e.data.interval;
    console.log("interval="+interval);
    if (timerID) {
      clearInterval(timerID);
      timerID=setInterval(function(){postMessage("tick");},interval)
    }
  }
  else if (e.data=="stop") {
    console.log("stopping");
    clearInterval(timerID);
    timerID=null;
  }
};

如何在scheduleNote()中安排声音(和拍摄):

if (beatNumber % 4 === 0) {
    playSound(samplebb[0], gainNode1);
 }
if (planet1play === true) {
    if (barNumber % 4 === 0)
        if (current16thNote % 1 === 0) {
            playSound(samplebb[26], planet1gain);
        }
}
if (shootx) {
    //  Weapon 1
    if (gun === 0) {
        if (beatNumber === 2 || beatNumber === 6 || beatNumber === 10 || beatNumber === 14) {
            shoot(bulletX, bulletY);
            playSound(samplebb[3], gainNode2);
        }
    }

更新

即使我在游戏中不渲染或更新任何内容,音频仍然存在问题。在较慢的机器上问题更加严重。您可以通过此处链接查看该游戏。

不知道为什么会出现这种情况,可能是某种音频缓存问题?有人有什么想法吗?


干杯,我已经放置了三个时间轴快照的链接,第二个根本不下降,所以你的意思是这可能只是因为浏览器还没有决定进行垃圾回收? - 00-BBB
2个回答

1
如果在连续调用“start”命令时运行多个“setInterval”,存在风险。如果它们堆叠在一起,可能会导致内存增加的原因。
我建议进行以下更改。您可以在“start”方法中简单地检查timerID是否存在,但集中方法将有助于跟踪。可以使用null参数调用“clearInterval()”,不会产生任何后果,只是会忽略它。
因此,本质上:
var timerID = null;
var interval = 100;

function tickBack() {           // share callback for timer
    postMessage("tick")
}

function startTimer() {             // centralize start method
    stopTimer();                    // can be called with no consequence even if id=null
    timerID = setInterval(tickBack, interval)
}

function stopTimer() {              // centralize stop method
    clearInterval(timerID);
    timerID = null;
}

onmessage = function(e){

  if (e.data === "start") {
    startTimer();
  }
  else if (e.data === "stop") {
    stopTimer()
  }
  else if (e.data.interval) {
    interval = e.data.interval;
    if (timerID) startTimer();
  }
};

谢谢,我现在正在尝试实验看看它是否有帮助。我有一个问题 - 最后的else if语句是用来做什么的?它会让节拍器立即开始而不等待play()函数吗? - 00-BBB
1
@00-BBB 抱歉,我重新组织时出了错。只需在那里添加一个条件即可(答案已更新)。 - user1693593
谢谢,这似乎有所帮助,但即使没有渲染敌人,音频仍然会中断,我会进行更多测试..!:S - 00-BBB
时间轴截图看起来仍然像原来的样子,这绝对意味着有问题吗?或者时间轴不一定显示内存泄漏?如果我强制进行垃圾回收,并且内存回到零,这是否意味着一切正常? - 00-BBB
1
如果GC负责清理并回收内存,那么就应该没问题了。 - user1693593

0

糟糕!我找到问题了!我的应用程序中创建了一个振荡器,但没有使用它,这导致填满了音频上下文

var osc = audioContext.createOscillator();
osc.connect( audioContext.destination );

http://users.sussex.ac.uk/~bc216/AX11/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接