如何在Firebase中实现分布式倒数计时器 [英] How to implement a distributed countdown timer in Firebase
问题描述
我有一个应用程序,其中一个用户托管游戏,然后其他用户可以对托管人的问题进行投票.从主持人发布问题的那一刻起,玩家就有20秒的投票时间.
如何在所有播放器的屏幕上显示倒数计时器并使它们与主机保持同步?
许多开发人员都陷在这个问题上,因为他们试图在所有用户之间同步倒数计时本身.这很难保持同步,并且容易出错.但是,有一种更简单的方法,我在许多项目中都使用过这种方法.
每个客户需要显示其倒数计时器的所有三件事都是相当静态的信息:
- 问题发布的时间,即计时器开始的时间.
- 从那一刻起,他们需要数的时间.
- 客户端相对于中央计时器的相对偏移量.
我们将使用数据库的服务器时间作为第一个值,第二个值仅来自主机代码,而 当我们执行上面的代码时,它将当前时间写入数据库,并且这是20秒的倒计时.由于我们使用 现在,让我们看看另一个用户如何读取该数据.与Firebase一样,我们将使用 执行此 因此,我们假设我们正在调用一个新的数据快照以开始刚刚开始的倒计时.我们如何在所有屏幕上显示准确的倒数计时器? 我们首先使用以下方法从数据库中获取值: 我们还需要估计本地客户端之间的时间以及服务器上的时间.Firebase SDK首次连接服务器时会估算该时间,我们可以从客户端中的 在运行良好的系统中, 接下来,我们将启动一个间隔计时器,该计时器每100毫秒左右接收一次呼叫: 然后间隔的每个计时器到期,我们将计算剩余时间: 最后,我们以合理的格式记录剩余时间,如果计时器已过期,则将其停止: 上面的代码中肯定还有一些清理工作,例如,当一个新的倒计时开始时(仍在进行中),但是整体方法效果很好,并且可以轻松扩展到成千上万的用户. 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: 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. 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: 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 Now let's see how the other user's read this data. As usual with Firebase, we'll use an When this 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: 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 In a well running system, the Next up we'll start an interval timer, which gets calls every 100ms or so: Then every timer our interval expires, we're going to calculate the time that is left: And then finally we log the remaining time, in a reasonable format and stop the timer if it has expired: 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屋! ServerValue.TIMESTAMP
写入时间,因此数据库将在服务器上写入时间,因此它不会受到主机的本地时间(或偏移量)的影响./p> on()
侦听器,这意味着我们的代码将在数据写入时主动侦听:
ref.on("value",(snapshot)=> {...});
ref.on(...
"代码时,它将立即从数据库中读取当前值并运行回调,但随后它还将继续监听数据库的更改并运行再次写入时再次输入代码.
ref.on("value",(snapshot)=> {const seconds = snapshot.val().seconds;const startAt = snapshot.val().startAt;...});
.info/serverTimeOffset
读取它:
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
是一个正值,表示我们对服务器的延迟(以毫秒为单位).但是,如果我们的本地时钟有偏移,它也可能是负值.无论哪种方式,我们都可以使用此值来显示更准确的倒数计时器.
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)});
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)});
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);如果(timeLeft< 0){clearInterval(interval);console.log(剩余的0.0");}别的 {console.log(`$ {Math.floor(timeLeft/1000)}.$ {timeLeft%1000}`);}},100)});
const database = firebase.database();
const ref = database.ref("countdown");
ref.set({
startAt: ServerValue.TIMESTAMP,
seconds: 20
});
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.
on()
listener, which means our code is actively listening for when the data gets written:ref.on("value", (snapshot) => {
...
});
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.ref.on("value", (snapshot) => {
const seconds = snapshot.val().seconds;
const startAt = snapshot.val().startAt;
...
});
.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
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.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)
});
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)
});
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)
});