将 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()
功能暂时可以,但我猜它会给您带来麻烦。
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()
功能暂时可以,但我猜它会给您带来麻烦。