如何在 Firebase 中实现分布式倒数计时器 [英] How to implement a distributed countdown timer in Firebase

查看:32
本文介绍了如何在 Firebase 中实现分布式倒数计时器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个应用,其中一个用户托管游戏,然后其他用户可以对主机提出的问题进行投票.从主持人发布问题的那一刻起,玩家有 20 秒的时间进行投票.

I have an app where one user hosts a game, and then other users can vote on questions from the host. From the moment the host posts the question, the players have 20 seconds to vote.

如何在所有玩家的屏幕上显示倒数计时器并使其与主机保持同步?

How can I show a countdown timer on all player's screens and keep them synchronized with the host?

推荐答案

许多开发人员陷入这个问题,因为他们试图在所有用户之间同步倒计时本身.这很难保持同步,而且容易出错.然而,有一种更简单的方法,我在许多项目中都使用过这种方法.

Many developers get stuck on this problem because they try to synchronize the countdown itself across all users. This is hard to keep in sync, and error prone. There is a much simpler approach however, and I've used this in many projects.

每个客户端需要显示其倒数计时器的三件事是相当静态的信息:

All each client needs to show its countdown timer are three things of fairly static information:

  1. 发布问题的时间,即计时器开始的时间.
  2. 从那一刻起他们需要计算的时间.
  3. 客户端相对于中央计时器的相对偏移量.

我们将使用数据库的服务器时间作为第一个值,第二个值来自主机代码,相对偏移 是 Firebase 为我们提供的值.

We're going to use the server time of the database for the first value, the second value is just coming from the hosts code, and the relative offset is a value that Firebase provides for us.

下面的代码示例是用 JavaScript 为网络编写的,但使用相同的方法(和非常相似的代码),并应用于 iOS、Android 和大多数其他实现实时侦听器的 Firebase SDK.

The code samples below are written in JavaScript for the web, but the same approach (and quite similar code) and be applied in iOS, Android and most other Firebase SDKs that implement realtime listeners.

让我们首先写入开始时间和间隔到数据库.忽略安全规则和验证,这可以很简单:

Let's first write the starting time, and interval to the database. Ignoring security rules and validation, this can be as simple as:

const database = firebase.database();
const ref = database.ref("countdown");
ref.set({
  startAt: ServerValue.TIMESTAMP,
  seconds: 20
});

当我们执行上面的代码时,它会将当前时间写入数据库,这是一个 20 秒的倒计时.由于我们用ServerValue.TIMESTAMP来写时间,数据库会把时间写在服务器上,所以不会受到主机本地时间(或偏移量)的影响.

When we execute the above code, it writes the current time to the database, and that this is a 20 second countdown. Since we're writing the time with ServerValue.TIMESTAMP, the database will write the time on the server, so there's no chance if it being affected by the local time (or offset) of the host.

现在让我们看看其他用户如何读取这些数据.与 Firebase 一样,我们将使用 on() 侦听器,这意味着我们的代码会在数据写入时主动侦听:

Now let's see how the other user's read this data. As usual with Firebase, we'll use an on() listener, which means our code is actively listening for when the data gets written:

ref.on("value", (snapshot) => {
  ...
});

当这个 ref.on(... 代码执行时,它会立即从数据库中读取当前值并运行回调.但它也会继续监听数据库的变化,并运行发生另一次写入时再次执行代码.

When this ref.on(... code executes, it immediately reads the current value from the database and runs the callback. But it then also keeps listening for changes to the database, and runs the code again when another write happens.

所以让我们假设我们收到一个新的数据快照,用于刚刚开始的倒计时.我们如何在所有屏幕上显示准确的倒数计时器?

So let's assume we're getting called with a new data snapshot for a countdown that has just started. How can we show an accurate countdown timer on all screens?

我们将首先从数据库中获取值:

We'll first get the values from the database with:

ref.on("value", (snapshot) => {
  const seconds = snapshot.val().seconds;
  const startAt = snapshot.val().startAt;
  ...
});

我们还需要估计本地客户端和服务器上的时间之间的时间.Firebase SDK 在它第一次连接到服务器时估计这个时间,我们可以从客户端的 .info/serverTimeOffset 读取它:

We also need to estimate how much time there is between our local client, and the time on the server. The Firebase SDK estimates this time when it first connects to the server, and we can read it from .info/serverTimeOffset in the client:

const serverTimeOffset = 0;
database.ref(".info/serverTimeOffset").on("value", (snapshot) => { serverTimeOffset = snapshot.val() });
ref.on("value", (snapshot) => {
  const seconds = snapshot.val().seconds;
  const startAt = snapshot.val().startAt;
  
});

在运行良好的系统中,serverTimeOffset 是一个正值,表示我们到服务器的延迟(以毫秒为单位).但如果我们的本地时钟有偏移,它也可能是一个负值.无论哪种方式,我们都可以使用此值来显示更准确的倒数计时器.

In a well running system, the serverTimeOffset is a positive value indicating our latency to the server (in milliseconds). But it may also be a negative value, if our local clock has an offset. Either way, we can use this value to show a more accurate countdown timer.

接下来我们将启动一个间隔计时器,它每 100 毫秒左右调用一次:

Next up we'll start an interval timer, which gets calls every 100ms or so:

const serverTimeOffset = 0;
database.ref(".info/serverTimeOffset").on("value", (snapshot) => { serverTimeOffset = snapshot.val() });
ref.on("value", (snapshot) => {
  const seconds = snapshot.val().seconds;
  const startAt = snapshot.val().startAt;
  const interval = setInterval(() => {
    ...
  }, 100)
});

然后每个计时器我们的时间间隔到期,我们将计算剩余的时间:

Then every timer our interval expires, we're going to calculate the time that is left:

const serverTimeOffset = 0;
database.ref(".info/serverTimeOffset").on("value", (snapshot) => { serverTimeOffset = snapshot.val() });
ref.on("value", (snapshot) => {
  const seconds = snapshot.val().seconds;
  const startAt = snapshot.val().startAt;
  const interval = setInterval(() => {
    const timeLeft = (seconds * 1000) - (Date.now() - startAt - serverTimeOffset);
    ...
  }, 100)
});

最后,我们以合理的格式记录剩余时间,并在超时时停止计时器:

And then finally we log the remaining time, in a reasonable format and stop the timer if it has expired:

const serverTimeOffset = 0;
database.ref(".info/serverTimeOffset").on("value", (snapshot) => { serverTimeOffset = snapshot.val() });
ref.on("value", (snapshot) => {
  const seconds = snapshot.val().seconds;
  const startAt = snapshot.val().startAt;
  const interval = setInterval(() => {
    const timeLeft = (seconds * 1000) - (Date.now() - startAt - serverTimeOffset);
    if (timeLeft < 0) {
      clearInterval(interval);
      console.log("0.0 left)";
    }
    else {
      console.log(`${Math.floor(timeLeft/1000)}.${timeLeft % 1000}`);
    }
  }, 100)
});

上面的代码肯定还有一些清理工作要做,例如当一个新的倒计时开始而一个仍在进行中时,但整体方法运行良好并且可以轻松扩展到数千个用户.

There's definitely some cleanup left to do in the above code, for example when a new countdown starts while one is still in progress, but the overall approach works well and scales easily to thousands of users.

这篇关于如何在 Firebase 中实现分布式倒数计时器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆