我正在使用Firebase实时数据库进行Unity
应用构建。为了建立“朋友”排行榜,数据库将跟踪用户的朋友和分数。
数据库具有以下结构:
scores{
user id : score
}
Users:{
Id: {
ageRange
email
name
friendlist : {
multiple friends user ids
}
}
}
问题是为了获取应用程序必须进行大量api调用的分数和每个朋友的名字。如果我正确了解Firebase,请了解。如果用户有10个朋友,则排行榜填充之前将需要21个呼叫。
我想出了用c#编写的以下代码:
List<UserScore> leaderBoard = new List<UserScore>();
db.Child("users").Child(uid).Child("friendList").GetValueAsync().ContinueWith(task => {
if (task.IsCompleted)
{
//foreach friend
foreach(DataSnapshot h in task.Result.Children)
{
string curName ="";
int curScore = 0;
//get his name in the user table
db.Child("users").Child(h.Key).GetValueAsync().ContinueWith(t => {
if (t.IsCompleted)
{
DataSnapshot s = t.Result;
curName = s.Child("name").Value.ToString();
//get his score from the scores table
db.Child("scores").Child(h.Key).GetValueAsync().ContinueWith(q => {
if (q.IsCompleted)
{
DataSnapshot b = q.Result;
curScore = int.Parse(b.Value.ToString());
//make new userscore and add to leaderboard
leaderBoard.Add(new UserScore(curName, curScore));
Debug.Log(curName);
Debug.Log(curScore.ToString());
}
});
}
});
}
}
});
还有其他方法吗?我看过看过Firebase教程的多个堆栈溢出问题,但是我没有找到任何更简单或更有效的方法来完成工作。
答案 0 :(得分:2)
在不复制数据/重组数据库的情况下,无法减少API调用的次数。但是,减少API调用并不一定意味着整体读取策略会更快/更好。我的建议是优化读取策略,以减少整体数据读取,并在可能的情况下同时进行读取。
这是我推荐的解决方案,因为它不包括多余的数据,也不包括管理数据的一致性。
您的代码应如下所示:
(免责声明:我不是C#程序员,所以可能会有一些错误)
List<UserScore> leaderBoard = new List<UserScore>();
db.Child("users").Child(uid).Child("friendList").GetValueAsync().ContinueWith(task => {
if (task.IsCompleted)
{
//foreach friend
foreach(DataSnapshot h in task.Result.Children)
{
// kick off the task to retrieve friend's name
Task nameTask = db.Child("users").Child(h.Key).Child("name").GetValueAsync();
// kick off the task to retrieve friend's score
Task scoreTask = db.Child("scores").Child(h.Key).GetValueAsync();
// join tasks into one final task
Task finalTask = Task.Factory.ContinueWhenAll((new[] {nameTask, scoreTask}), tasks => {
if (nameTask.IsCompleted && scoreTask.IsCompleted) {
// both tasks are complete; add new record to leaderboard
string name = nameTask.Result.Value.ToString();
int score = int.Parse(scoreTask.Result.Value.ToString());
leaderBoard.Add(new UserScore(name, score));
Debug.Log(name);
Debug.Log(score.ToString());
}
})
}
}
});
上面的代码通过不提取friend
的所有用户数据(即name
,email
,friendlist
等)来改善整体读取策略,并且通过与name
同时拉动score
。
name
复制到scores
表 如果这还不够理想,则可以始终在其friend
表中复制name
的{{1}}。如下所示:
score
这将使您每个scores: {
<user_id>: {
name: <user_name>,
score: <user_score>
}
}
仅打一个电话,而不是两个。但是,您仍将读取相同数量的数据,并且必须管理数据的一致性(要么使用Firebase Function传播用户名更改,要么写到两个地方)。 / p>
friend
表合并到scores
表中 如果您不想管理一致性问题,可以将users
表合并到scores
表中。
您的结构类似于:
users
但是,在这种情况下,您将读取更多数据(users: {
<user_id>: {
name: <user_name>,
...,
score: <user_score>
}
}
,email
等)
我希望这会有所帮助。
答案 1 :(得分:2)
尽管以下内容不会减少调用数量,但它将创建用于检索数据的任务并同时运行所有任务,并返回所需用户分数列表。
var friendList = await db.Child("users").Child(uid).Child("friendList").GetValueAsync();
List<Task<UserScore>> tasks = new List<Task<UserScore>>();
//foreach friend
foreach(DataSnapshot friend in friendList.Children) {
var task = Task.Run( async () => {
var friendKey = friend.Key;
//get his name in the user table
var getName = db.Child("users").Child(friendKey).Child("name").GetValueAsync();
//get his score from the scores table
var getScore = db.Child("scores").Child(friendKey).GetValueAsync();
await Task.WhenAll(getName, getScore);
var name = getName.Result.Value.ToString();
var score = int.Parse(getScore.Result.Value.ToString());
//make new userscore to add to leader board
var userScore = new UserScore(name, score);
Debug.Log($"{name} : {score}");
return userScore;
});
tasks.Add(task);
}
var scores = await Task.WhenAll(tasks);
List<UserScore> leaderBoard = new List<UserScore>(scores);
答案 2 :(得分:1)
这主要是数据库结构问题。
首先,您需要leaderboard
表。
leaderboard: {
<user_id>: <score>
}
第二,您需要users
表。
users: {
<user_id>: {
name: <user_name>,
...,
score: <user_score>,
friendlist: {
multiple friends user ids
}
}
}
并且您必须同时更新leaderboard
和users
的分数。
如果您想避免使用Callback hell
。
您也可以这样尝试。 (此代码为JAVA)
// Create a new ThreadPoolExecutor with 2 threads for each processor on the
// device and a 60 second keep-alive time.
int numCores = Runtime.getRuntime().availableProcessors();
ExecutorService executor = new ThreadPoolExecutor(
numCores * 2,
numCores * 2,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>()
);
Tasks.call(executor, (Callable<Void>) () -> {
Task<Token> getStableTokenTask = NotificationUtil.getStableToken(token);
Token stableToken;
stableToken = Tasks.await(getStableTokenTask);
if (stableToken != null) {
Task<Void> updateStableTokenTask = NotificationUtil.updateStableToken(stableToken.getId(), versionCode, versionName);
Tasks.await(updateStableTokenTask);
}
if (stableToken == null) {
Token newToken = new Token(token, versionCode, versionName);
Task<Void> insertStableTokenTask = NotificationUtil.insertStableToken(newToken);
Tasks.await(insertStableTokenTask);
}
return null;
}).continueWith((Continuation<Void, Void>) task -> {
if (!task.isSuccessful()) {
// You will catch every exceptions from here.
Log.w(TAG, task.getException());
return null;
}
return null;
});