Обработка «задач» Firebase и реализация классов менеджеров (для Firebase) в Unity

#c# #firebase #unity3d #design-patterns #firebase-realtime-database

#c# #firebase #unity3d #шаблоны проектирования #firebase-база данных в реальном времени

Вопрос:

Я пишу код для игры, серверная часть которой полностью основана на Firebase. Я ожидаю использовать в игре функции аутентификации, базы данных, InstanceID, обмена сообщениями и облачных функций. Будучи начинающим программистом на C #, я впервые столкнулся с «задачами» C # с Firebase. Я собираюсь использовать базу данных много раз (например, обновление результатов, запросы друзей, чат, друг сделал это, друг сделал то). В основном я чувствую себя комфортно с одноэлементным шаблоном (GameManagers, EnemyManagers, SoundManagers и т.д.). Но с Firebase, поскольку большинство его вызовов асинхронны и реализуются через задачи. Я думаю, мне нужно по-другому обойти внедрение менеджеров.

Например, мне нужно отправить запрос на добавление в друзья конкретному другу. UIManager — это скрипт, который обрабатывает события пользовательского интерфейса и т.д. Я хотел бы вызвать метод из этого скрипта другому менеджеру (скажем, FriendsManager). Но мне нужно сначала проверить, является ли этот друг уже моим другом из базы данных или нет? Итак, что бы я сделал, это

 class UIManager
{
   void OnFriendRequestClicked(string friendId)
   {
      bool userExists = FriendsManager.instance.UserExists(friendId);
      if(userExists)
            // Proceed with Sending Request
               FriendsManager.instance.SendRequest(friendId);
       else 
          // Show a Dialogue that User ID is invalid
          ShowError("User Id is invalid");

       // NOTE: The above code block of "condition" is executed before 
       // the UserID is validated from FriendsManager
       // I know its because of Task. But how can I alter this code 
       // to do something in the similar pattern? 
   }
}

class FriendsManager
{
   bool UserExists(string userIdToCheck)
   {
    reference.Child("users").Child(userIdToCheck).GetValueAsync().ContinueWith(
  task=>
   {
       if(task.IsCompleted)
         {
             if(task.Result == null)
                  return false;    // (expected) Return false to Method "UserExists"
             else 
                  return true;    //(expected) Return true to Method "UserExists" 

       // But this won't actually return "bool" to the method,
      // it actually returns to its own "Task"
     //NOTE: -> How to Return from here to the Method? 
   )};   
}
  

Ответ №1:

Данные загружаются из Firebase асинхронно. Вместо ожидания / блокировки во время загрузки данных приложение продолжается. И затем, когда данные доступны, он вызывает ваш обратный вызов.

Вы можете легко увидеть это с помощью некоторых инструкций ведения журнала:

 Debug.Log("Before starting to load data");

reference.Child("users").Child(userIdToCheck).GetValueAsync().ContinueWith(task=> {
   Debug.Log("Data loaded");
});

Debug.Log("After starting to load data");
  

Когда вы запускаете этот код, он регистрирует:

Перед началом загрузки данных

После начала загрузки данных

Загруженные данные

Это, вероятно, не то, что вы ожидали, но прекрасно объясняет, почему вы не можете вернуть значение из обратного вызова: UserExists на этом этапе уже завершено.

Это означает, что любой код, которому требуется доступ к данным из базы данных, должен находиться внутри ContinueWith блока (или вызываться оттуда).


Самый простой подход — переместить код из вашего OnFriendRequestClicked в UserExists :

 bool UserExists(string userIdToCheck) {
  reference.Child("users").Child(userIdToCheck).GetValueAsync().ContinueWith(task=>
{
   if(task.IsCompleted)
   {
       if(task.Result == null)
           ShowError("User Id is invalid");
       else 
           FriendsManager.instance.SendRequest(friendId);
   )};   
}
  

Затем вы можете вызвать эту функцию без if после нее.


Приведенный выше подход отлично работает, но означает, что ваш UserExists метод больше не может использоваться повторно в разных случаях. Чтобы сделать его повторно используемым, вы можете передать свой собственный интерфейс обратного вызова в UserExists .

Например, с помощью Task :

 bool UserExists(string userIdToCheck, Action<bool> callback) {
  reference.Child("users").Child(userIdToCheck).GetValueAsync().ContinueWith(task=>
{
   if(task.IsCompleted)
   {
       if(task.Result == null)
           callback(false);
       else 
           callback(true);
   )};   
}
  

А затем для его вызова:

 FriendsManager.instance.UserExists(friendId, userExists => {
    if(userExists)
        FriendsManager.instance.SendRequest(friendId);
    else 
        ShowError("User Id is invalid");
})
  

Комментарии:

1. Это то, что я искал. Собственные обратные вызовы — это то, что я искал.

2. Ну, не знаю, поддерживают ли эти обратные вызовы C # 4.0 или нет?

3. У меня был другой вариант использования асинхронности. Но асинхронность была введена позже в C # 5.0. Я проверю, поддерживаются ли пользовательские обратные вызовы в C # 4.0 или нет. Однако спасибо за ваш ответ.

4. Насколько я мог определить, все это было довольно устоявшимся материалом C #. Но определенно прошло несколько лет с тех пор, как я писал C #, поэтому могут быть синтаксические ошибки. Если вы столкнулись с чем-либо, пожалуйста, прокомментируйте или (событие лучше) нажмите Редактировать, чтобы исправить их. 🙂