将 P5.js 绘图函数置于自定义循环函数中

Placing P5.js draw function in a custom loop function

P5.js 有自己的循环,它每秒调用 draw() 函数 FPS 次。但是,有一个函数 noLoop() 可以放在 setup() 函数中,它会禁用 P5.js 自己的循环。

所以我制作了自己的循环(这对我的项目来说是必需的),如下所示:

customLoop = function(){
  while(somethingIsTrue){
    //custom code here
    draw();
  }
}

所以我希望看到 canvas 更新得非常快,因为没有设置 FPS。但是,canvas 仅在 customLoop 完成后才重新绘制。

简而言之:当自定义循环仍在 运行ning 时 canvas 不会更新,它只会在自定义循环完成后显示更新。

如何在自己的循环中连续更新 canvas?


人们要求提供更多背景信息,所以我会尽力解释。我已经制作了 own JS library,可以轻松地为神经网络创建遗传算法。

我的库中的遗传算法设置如下:

var evol = new Evolution({
  size: 50,
  mutationRate: 0.05,
  networkSize: [4,10,1],
  mutationMethod: [Mutate.MODIFY_RANDOM_BIAS, Mutate.MODIFY_RANDOM_WEIGHT],
  crossOverMethod: [Crossover.UNIFORM, Crossover.AVERAGE],
  selectionMethod: [Selection.FITNESS_PROPORTIONATE],
  elitism: 5,
  fitnessFunction: function(network){
    //something here that computes for every network, AT THE SAME TIME showing it LIVE for the user to watch
  }
});

注意fitnessFunction中的注释。然后我运行一个遗传算法循环如下:

 var notFinished = true;
  while(notFinished){
    evol.evaluate();
    if(evol.getAverage() > 2000){
      notFinished = false;
      console.log('done');
    }
    evol.select();
    evol.crossOver();
    evol.mutate();
    evol.replace();
  };

所以如您所见,我的代码连续 运行 是遗传算法。但整个问题在于适应度函数。这个特定的遗传算法是为了训练神经网络如何玩 snake。所以在适应度函数中,我实际上是 运行 神经网络直到蛇死。但是,对于每条被测试的蛇,我希望用户 查看 神经网络的运行情况。所以我的适应度函数如下所示:

fitnessFunction = function(network){
  while(snakeIsNotDead){
    computeNeuralNetworkOutput();
    giveSnakeNewMovementDirectionBasedOnOutput();

    // most importantly
    redrawTheSnakeWithNewPosition(); // which is redraw() with P5.js
}

因此,对于测试的每一代,我都想看到所有 50 条蛇的表演。然而,网络完成后我看到的只是蛇在上次蛇评估中的最后位置。

根据文档,您希望避免直接调用 "draw()"。尝试类似的东西:

customLoop = function(){
    while(somethingIsTrue){
        //custom code here
        redraw();  // Causes the code inside "draw()" to execute once
    }
}

https://p5js.org/reference/#/p5/draw

特别说明:draw() 是自动调用的,永远不应显式调用。 它应该始终由 noLoop()、redraw() 和 loop() 控制。

此外,由于JavaScript是单线程的,所以只要while循环在滴答作响,其他任何东西都无法执行。这是一篇相关文章:

Asynchronous function in a while-loop

还有一个很好的、详尽的解释:

http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/

您可以尝试类似的方法:

var timerId = 0;

timerId = setTimeout(myCustomRedraw(), 10); // Every 10 milliseconds.
myCustomRedraw = function () {
    // custom code here
    if (<stop loop condition>) {
        clearTimeout(timer); // Will stop the timeout loop.
    }
    redraw()
}

请尝试 post 您的代码作为我们实际上可以 运行 的 MCVE。这是一个显示您的问题的小示例:

function setup(){
  createCanvas(500, 500);
  for(var i = 0; i < 600; i ++){
    redraw();
  }
}

function draw(){
  ellipse(random(width), random(height), 20, 20);
}

这将显示一个 canvas,上面已经绘制了一堆随机圆圈。我想你想要的是看到圆圈堆积起来,就像你是 "fast-forwarding" 素描一样。

使用您自己的循环有点可疑。 Processing 已经有自己的循环机制,它包含双缓冲 canvas 的逻辑。这就是为什么在绘图完成之前您看不到任何东西的原因。您可以只依赖 Processing 的内置计时机制。这是一个例子:

function setup(){
  createCanvas(500, 500);
  frameRate(120);
}

function draw(){
  if(frameCount == 600){
    frameRate(60);
  }
  ellipse(random(width), random(height), 20, 20);

}

请注意,如果您使用的设备 运行 限制了帧速率,这可能不起作用。但关键是您不需要创建自己的循环。只需使用 Processing 的内置循环逻辑。

编辑: 正如我所说,您看不到任何帧的原因是 Processing 是双缓冲的。这意味着 Processing 将等到它完成所有事件并将所有内容绘制到屏幕上。这是过于简单化了,但您可以将上述循环视为向事件队列添加 600 个重绘事件。处理通过它们,但在完成之前不会实际显示结果。

您可以通过在此事件队列清空后调用每次重绘来解决此问题。一种稍微老套的方法是使用 setTimeout() 函数:

function setup(){
  createCanvas(500, 500);
  for(var i = 0; i < 600; i ++){
    setTimeout(redraw, 0);
  }
}

function draw(){
  ellipse(random(width), random(height), 20, 20);
}

现在代码不再连续调用 redraw() 函数 600 次,而是将 600 个超时事件添加到事件队列中。它们中的每一个都将被单独处理,因此您会看到断断续续的帧。

我要说的是,这似乎仍然是实现您目标的一种怪异方式。如果我是你,我仍然会尝试将 draw() 函数与你的实际逻辑分开。让您的模拟设置变量,并让 draw() 函数使用这些变量。这就是解决这个问题的 "correct" 方法。使用 setTimeout() 功能暂时可以,但我猜它会给您带来麻烦。