【问题标题】:Firebase Javascript + P5.js: Asynchronous function getting in the way of redrawing the canvasFirebase Javascript + P5.js:异步函数妨碍重绘画布
【发布时间】:2017-11-04 03:36:29
【问题描述】:

我正在尝试创建一个应用程序,通过从存储圆圈的 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(); 
}

【问题讨论】:

  • 请尝试break your problem down into smaller pieces,一次只处理一件。例如,您能否编写一个从 Firebase 检索数据的简单程序?在你继续之前让它完美地工作。除此之外,你能创建一个显示一些硬编码点的程序吗?在将它们组合成一个程序之前,让它完美地工作。如果您在某个特定步骤上遇到困难,请发布该步骤的minimal reproducible example。祝你好运。
  • @KevinWorkman 我已将问题缩小到 background(40) 正在清除屏幕上绘制的所有圆圈,因为 drawCircles() 正在异步运行。

标签: javascript html firebase processing p5.js


【解决方案1】:

您的问题似乎只是每秒 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 现在都保存在in circles。比如circles/circle1circles/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 的另一位粉丝。

【讨论】:

    【解决方案2】:

    我查看了文档,这里是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
      }

    【讨论】:

    • 我试过你的代码,但不幸的是圆圈仍然不可见。
    【解决方案3】:

    问题不在于drawCircles() 是异步的——问题在于draw()frameRate() 处被调用,而background() 在绘制循环时每过一秒都会用纯色清除屏幕:请参阅draw referencebackground。如果您从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>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-30
      • 2020-04-22
      • 2021-08-24
      • 1970-01-01
      • 1970-01-01
      • 2014-11-17
      • 2021-09-11
      • 1970-01-01
      相关资源
      最近更新 更多