Firebase Javascript + P5.js:异步函数妨碍重绘 canvas
Firebase Javascript + P5.js: Asynchronous function getting in the way of redrawing the canvas
我正在尝试创建一个应用程序,通过从存储圆圈 x 和 y 坐标的 Firebase 数据库中读取信息,在 canvas 上绘制圆圈。然而,执行下面的代码,什么也没有产生,没有任何圆圈的符号,因为函数 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();
}
我查看了文档,这里是 example 他们建议如何处理获取的数据。在你的情况下,尝试将获取和绘制分开,使用一些全局变量缓存你的数据:
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 reference and 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 缓冲区上积累你的圈子。每一帧都将圆圈缓冲区重绘到 canvas 上,然后在顶部绘制临时元素(如鼠标指示器等)。
这里有一个例子:canvas的每一帧都用background()
清除,然后pg
缓冲区被绘制到canvas上,然后一个白色的圆圈是在鼠标上绘制。因为背景会清除屏幕,所以白色圆圈不会在帧与帧之间留下痕迹 -- 但红色圆圈会绘制到未清除的图形缓冲区,因此它们会持续存在。
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>
您的问题似乎只是每秒 60 帧,这导致了竞跑情况。 Firebase .once
将在完成抓取后执行异步操作,而 P5 不会等待它抓取,因为它会坚持其帧率计时。
在这个具体案例中,我有多个建议,希望能让您非常接近您想要的结果。
1 - 重组代码
您当前的代码结构存在两个问题。
案例 1:您当前的代码会让我认为您的圈子在数据库中实时更新,您需要保持最新状态,所以您不断获取他们的最新位置。如果是这种情况,您应该使用 .on("value")
而不是 .once("value")
并让 firebase 在圆圈发生变化时向您发送更新,而不是每秒询问 60 次以节省往返请求时间。如果是这种情况:请参阅下面我的 解决方案 1。
情况 2:如果你的圈子在数据库中没有实时更新,而你只想要整个圈子列表,那么你每秒获取列表 60 次原因。您应该改为在设置时使用 .once
获取列表,然后在 draw()
中遍历该列表。请参阅下面的解决方案 2。
2 - 重构你的数据库
在任何一种情况下,您当前的数据库模型都要求您在循环中不断获取数据。这意味着您提出的请求与 circleCount
一样多。这对您的使用不利,仅仅是因为每个请求都需要额外的行程时间,而我们正在努力减少所花费的时间,以便更接近实时。 (或匹配帧率)
目前您的圈子似乎在 root 中保存为 circles1
circles2
等,因为您正在使用 .ref("circles" + j)
检索它们.这样你就可以像这样保存你的圈子:.ref("circles/" + j)
这意味着每个 circle
现在都保存在 中 circles
。像 circles/circle1
circles/circle2
等等
这样做的好处是,现在您不需要向 firebase 获取所有圈子的额外请求。 Firebase 具有非常方便的功能,例如 forEach 可以通过单个请求遍历所有子项。
3 - 在您的 firebase 回调中清除背景
目前,您以帧速率特定的方式清除背景。这意味着如果您的每个 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);
});
}
基本上这将继续绘制我们从 firebase 获得的最后一个圆圈位置,直到我们获得一个新的圆圈位置。 (因此 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 的另一位粉丝。
我正在尝试创建一个应用程序,通过从存储圆圈 x 和 y 坐标的 Firebase 数据库中读取信息,在 canvas 上绘制圆圈。然而,执行下面的代码,什么也没有产生,没有任何圆圈的符号,因为函数 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();
}
我查看了文档,这里是 example 他们建议如何处理获取的数据。在你的情况下,尝试将获取和绘制分开,使用一些全局变量缓存你的数据:
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 reference and 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 缓冲区上积累你的圈子。每一帧都将圆圈缓冲区重绘到 canvas 上,然后在顶部绘制临时元素(如鼠标指示器等)。
这里有一个例子:canvas的每一帧都用background()
清除,然后pg
缓冲区被绘制到canvas上,然后一个白色的圆圈是在鼠标上绘制。因为背景会清除屏幕,所以白色圆圈不会在帧与帧之间留下痕迹 -- 但红色圆圈会绘制到未清除的图形缓冲区,因此它们会持续存在。
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>
您的问题似乎只是每秒 60 帧,这导致了竞跑情况。 Firebase .once
将在完成抓取后执行异步操作,而 P5 不会等待它抓取,因为它会坚持其帧率计时。
在这个具体案例中,我有多个建议,希望能让您非常接近您想要的结果。
1 - 重组代码
您当前的代码结构存在两个问题。
案例 1:您当前的代码会让我认为您的圈子在数据库中实时更新,您需要保持最新状态,所以您不断获取他们的最新位置。如果是这种情况,您应该使用
.on("value")
而不是.once("value")
并让 firebase 在圆圈发生变化时向您发送更新,而不是每秒询问 60 次以节省往返请求时间。如果是这种情况:请参阅下面我的 解决方案 1。情况 2:如果你的圈子在数据库中没有实时更新,而你只想要整个圈子列表,那么你每秒获取列表 60 次原因。您应该改为在设置时使用
.once
获取列表,然后在draw()
中遍历该列表。请参阅下面的解决方案 2。
2 - 重构你的数据库
在任何一种情况下,您当前的数据库模型都要求您在循环中不断获取数据。这意味着您提出的请求与 circleCount
一样多。这对您的使用不利,仅仅是因为每个请求都需要额外的行程时间,而我们正在努力减少所花费的时间,以便更接近实时。 (或匹配帧率)
目前您的圈子似乎在 root 中保存为 circles1
circles2
等,因为您正在使用 .ref("circles" + j)
检索它们.这样你就可以像这样保存你的圈子:.ref("circles/" + j)
这意味着每个 circle
现在都保存在 中 circles
。像 circles/circle1
circles/circle2
等等
这样做的好处是,现在您不需要向 firebase 获取所有圈子的额外请求。 Firebase 具有非常方便的功能,例如 forEach 可以通过单个请求遍历所有子项。
3 - 在您的 firebase 回调中清除背景
目前,您以帧速率特定的方式清除背景。这意味着如果您的每个 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);
});
}
基本上这将继续绘制我们从 firebase 获得的最后一个圆圈位置,直到我们获得一个新的圆圈位置。 (因此 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 的另一位粉丝。