Firebase Javascript+P5.js:异步函数阻碍了重新绘制画布



我正试图创建一个应用程序,通过从存储圆的x和y坐标的Firebase数据库中读取信息,在画布上绘制圆。然而,执行下面的代码只会产生任何结果,而不会产生任何圆圈符号,因为函数drawCricles是异步运行的,因此命令background(40)会在绘制圆圈之前清除所有内容。

这是我的代码:

function setup() {
createCanvas(windowWidth, windowHeight); 
background(40); 
stroke(80); 
smooth();
frameRate(60);
}
function drawCircles() {
firebase.database().ref("circles").once("value", function(snapshot) {
var snapshotVal = snapshot.val();
var circleCount = snapshotVal.numCircles;
for (var j = 0; j < circleCount; j++) {
firebase.database().ref("circles" + j).once("value", function(snapshot) {
var snapshotValue = snapshot.val();
fill(143, 2, 2);
ellipse(snapshotValue.xPos, 50, 50);
});
}
});
}
function draw() {
stroke(80);
background(40);
stroke(0);
drawCircles(); 
}

您的问题似乎只是每秒60帧,这会导致竞速情况。Firebase.once在完成获取时将执行异步,P5不会等待它获取,因为它将坚持其帧速率计时。

在这个特定的案例中,我有多个建议,希望能让你非常接近你想要的结果。

1-重组您的代码

您的代码的当前结构存在两个问题。

  • 案例1:您当前的代码会让我认为您的圆圈在数据库中是实时更新的,您需要保持最新,所以您会不断获取它们的最新位置。如果是这种情况,您应该使用.on("value")而不是.once("value"),并让firebase在圆圈发生变化时向您发送更新,而不是每秒询问60次以节省往返请求时间。如果是这种情况:请参阅下面的解决方案1

  • 情况2:如果你的圆圈没有在数据库中实时更新,而你只想要完整的圆圈列表,那么你会无缘无故地每秒获取列表60次。您应该在设置时使用.once获取列表,然后在draw()中迭代该列表。请参阅下面的解决方案2

2-重组数据库

在任何一种情况下,当前的数据库模型都要求您保持循环获取。这意味着你的请求数量和你的circleCount一样多。这对您的使用不利,因为每个请求都需要额外的行程时间,我们正在努力减少所需时间,以便更接近实时(或匹配帧速率)

目前,您的圆似乎保存为circles1circles2等,所有这些都位于,因为您正在使用.ref("circles" + j)来检索它们。将其设置为这样保存您的圆圈:.ref("circles/" + j),这意味着每个circle现在都保存在circles中。如circles/circle1circles/circle2

这样做的好处是,现在你不需要额外的请求来获得firebase的所有圈子。Firebase有一些非常方便的东西,比如forEach,可以通过一个请求遍历所有子级。

3-清除你的火球回调中的背景

目前,您以特定于帧速率的方式清除背景。这意味着,如果你的每个firebase调用花费的时间超过1/60秒(16毫秒),你就会清除后台并继续前进。即使在我们构建数据库后,你达到这一速度的几率也很低。因此,我建议首先使用30fps,这也会将对firebase的调用次数减少到每秒30次。

解决方案1

如果你的圆圈在数据库中更新(例如,由其他游戏玩家或其他人更新,并且你希望你的代码始终显示最新的xPos)

var latestCirclePositionsSnapshot;
function setup() {
createCanvas(windowWidth, windowHeight); 
background(40); 
stroke(80); 
smooth();
frameRate(60);
firebase.database().ref("circles").on("value", function(snapshot) {
// got a new value from database, so let's save this in a global variable. 
latestCirclePositionsSnapshot = snapshot;
// we will keep drawing this update until we get a new one from the database.
});
}
function draw() {
drawCircles(); 
}
function clearBackground () {
stroke(80);
background(40);
}
function drawCircles() {
clearBackground();
stroke(0);  
latestCirclePositionsSnapshot.forEach(function(circleSnapshot) {  
// circleData will be the actual contents of each circle
var circleData = circleSnapshot.val();
fill(143, 2, 2);
ellipse(circleData.xPos, 50, 50);
});
}

基本上,这将继续绘制我们从燃烧基地得到的最后一个圆圈位置,直到我们得到一个新的。(所以P5将以60fps的速度保持刷新,但你的firebase更新将像firebase可以运行和从firebase获取一样实时。)

解决方案2

如果你的数据库中没有实时更新,你只想通过一次从firebase获取数据来画圆圈(例如,根据一些数据绘制一些点)

var circlePositions;
var gotPositions = false;
function setup() {
createCanvas(windowWidth, windowHeight); 
background(40); 
stroke(80); 
smooth();
frameRate(60);
firebase.database().ref("circles").once("value", function(snapshot) {
// got the circle values from the database
// let's store them and we'll keep drawing them forever. 
circlePositions = snapshot;
gotPositions = true;
});
}
function draw() {
drawCircles(); 
}
function clearBackground () {
stroke(80);
background(40);
}
function drawCircles() {
clearBackground();
stroke(0); 
if (gotPositions) {
circlePositions.forEach(function(circleSnapshot) {  
// circleData will be the actual contents of each circle
var circleData = circleSnapshot.val();
fill(143, 2, 2);
ellipse(circleData.xPos, 50, 50);
});
} else {
// Display some text here like "LOADING DATA FROM SERVERS..." 
}
}

希望这些帮助:)很高兴看到Processing&Firebase。

我查看了文档,下面是他们建议如何处理提取的数据的示例。在您的情况下,尝试将提取和绘制分开,使用一些全局变量缓存数据:

var circles = [];
function fetchData() {
firebase.database().ref("circles").once("value",
function(snapshot) {
var snapshotVal = snapshot.val();
var circleCount = snapshotVal.numCircles;

circles = [];
for (var j = 0; j < circleCount; j++) {
firebase.database().ref("circles" + j).once("value",                 function(snapshot) {
circles.push(snapshot.val());
});
}
});
}
function setup() {
createCanvas(windowWidth, windowHeight); 
background(40); 
stroke(80); 
smooth();
frameRate(60);
fetchData();
}
function drawCircles() {
circles.forEach(function (snapshotValue) {
var snapshotValue = snapshot.val();
fill(143, 2, 2);
ellipse(snapshotValue.xPos, 50, 50);
});
}
function draw() {
stroke(80);
background(40);
stroke(0);
drawCircles(); 
}

如果您需要始终显示相关数据,请尝试使用setInterval调用fetchData函数,如:

function setup() {
createCanvas(windowWidth, windowHeight); 
background(40); 
stroke(80); 
smooth();
frameRate(60);
setInterval(fetchData, 5000); //will call fetchData every 5000 ms
}

问题不在于drawCircles()是异步的——问题在于draw()frameRate()被调用,而background()在绘制循环时每几分之一秒都会用纯色清除屏幕:请参阅绘制参考和背景。如果从draw()中删除线background(40),则不会清除每一帧的屏幕,并且绘制的圆将根据需要累积。这比每帧重新绘制所有Firebase数据更简单。

下面的示意图演示了这个概念:background()只在setup()期间调用,而在draw()期间不调用,因此屏幕区域被着色一次,然后逐渐被累积的圆圈覆盖。

function setup() {
createCanvas(400, 200);
frameRate(5)
background(40);
}
function drawCircles() {
fill(143, 2, 2);
ellipse(random(width), 50, 50);
}
function draw() {
// background(40);
drawCircles();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.16/p5.js"></script>
<html>
<head>
</head>
<body>
</body>
</html>

如果你想在每帧中积累一些东西擦除其他东西,那么你需要在createGraphics缓冲区上积累你的圆圈。每一帧都会在画布上重新绘制圆圈缓冲区,然后在顶部绘制短暂的元素(如鼠标指示器等)。

这里有一个例子:每个帧都用background()清除画布,然后在画布上绘制pg缓冲区,然后在鼠标处绘制一个白色圆圈。因为背景会清除屏幕,所以白色圆圈不会在每帧之间留下痕迹,但红色圆圈会被绘制到未清除的图形缓冲区,因此它们会持续存在。

var pg;
function setup() {
createCanvas(400, 200);
pg = createGraphics(400, 200);
background(40);
}
function drawCircles() {
pg.fill(143, 2, 2);
pg.ellipse(random(pg.width), 50, 50);
}
function draw() {
background(40);
drawCircles();
image(pg,0,0);
fill(255);
ellipse(mouseX,mouseY,50,50);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.16/p5.js"></script>
<html>
<head>
</head>
<body>
</body>
</html>

最新更新