heroiclabs / nakama-unity

Unity client for Nakama server.
https://heroiclabs.com/docs/unity-client-guide
Other
411 stars 75 forks source link

SendMatchStateAsync() echo to sender #83

Closed ReGaSLZR closed 4 years ago

ReGaSLZR commented 4 years ago

Hello, guys.

In this “Send Data Messages” documentation you guys have on this link:

"A user in a match can send data messages which will be received by all other opponents."

But there are overloads of ISocket.SendMatchStateAsync() to specify players you want to receive your message (match state code).

I did try this:

await m_socket.SendMatchStateAsync(m_currentMatch.Id, (int)stateCode, string.Empty, m_playersInMatch);

where m_playersInMatch is a list of all presences in the match (the sender included) but here's the deal:

Linked forum post: https://forum.heroiclabs.com/t/sendmatchstateasync-for-all-players-including-the-sender/363 Linked gitter question: https://gitter.im/heroiclabs/nakama?at=5da405af894dee56e53c9bee#

P.S. I'm trying to send a match state code to all players in the match. All players including the sender of the state code.

ReGaSLZR commented 4 years ago

Sample script:

using Nakama;
using System;
using System.Collections.Generic;

//other import statements go here...

public class Sample : MonoBehaviour
{

  private IClient m_client;
  private ISocket m_socket;
  private IMatch m_currentMatch;
  private ICollection<IUserPresence> m_playersInMatch = new List<IUserPresence>();
  private string m_cachedHostPlayerID;

  private void Start()
  {
      InitClient();
      SubscribeCallbacks();
  }

  private void InitClient() {
    //init the client and socket here...
  }

  private void UpdateUserListUI() {
    //display the list of players in the match using m_playersInMatch
  }

  private void CheckHostPlayer()
  {
      //identify the host player here...
  }

  private void SubscribeCallbacks()
  {
      m_socket.ReceivedMatchmakerMatched += OnReceiveMatchmakerMatched;
      m_socket.ReceivedMatchPresence += OnReceiveMatchPresence;
      m_socket.ReceivedMatchState += OnReceiveMatchState;
  }

  private async void OnReceiveMatchmakerMatched(IMatchmakerMatched matched)
  {
      LogUtil.PrintInfo(GetType(), $"OnReceiveMatchmakerMatched() called.");

      try
      {
          m_currentMatch = await m_socket.JoinMatchAsync(matched);
          m_playersInMatch.Clear();
          CacheAllPlayersFromMatchmake(matched.Users);
          CheckHostPlayer();
      }
      catch (Exception exception)
      {
          //do stuff here...
      }
  }

  private void OnReceiveMatchPresence(IMatchPresenceEvent presenceEvent)
  {
      LogUtil.PrintInfo(GetType(), $"OnReceiveMatchPresence() called.");

      bool hasUpdate = UpdateCachedPlayerList(presenceEvent);

      if (hasUpdate)
      {
          CheckHostPlayer();
          UpdateUserListUI();
      }
      else
      {
          LogUtil.PrintInfo(GetType(), $"OnReceiveMatchPresence(): No change in Player List.");
      }

  }

  private void OnReceiveMatchState(IMatchState matchState)
  {
      LogUtil.PrintInfo(GetType(), $"OnReceiveMatchState() called. OpCode is: {matchState.OpCode}");
  }

  private void CacheAllPlayersFromMatchmake(IEnumerable<IMatchmakerUser> matchedUsers)
   {
       foreach (IMatchmakerUser user in matchedUsers)
       {
           if (!m_playersInMatch.Contains(user.Presence))
           {
               m_playersInMatch.Add(user.Presence);
           }
       }
   }

   private bool UpdateCachedPlayerList(IMatchPresenceEvent matchedEvent)
   {
       bool hasUpdate = false;

       foreach (IUserPresence userLeft in matchedEvent.Leaves)
       {
           if (m_playersInMatch.Contains(userLeft))
           {
               hasUpdate = true;
               m_playersInMatch.Remove(userLeft);
           }
       }

       foreach (IUserPresence newUser in matchedEvent.Joins)
       {
           if (!m_playersInMatch.Contains(newUser))
           {
               hasUpdate = true;
               m_playersInMatch.Add(newUser);
           }

       }

       return hasUpdate;
   }

   public async void RequestMatchmake(string queryFilter, int minPlayers, int maxPlayers)
   {
      //do stuff here...
   }

   public async void SendMatchStateCode(
            NakamaMatchStateCode stateCode,
            string payLoad = "")
    {
        LogUtil.PrintInfo(GetType(), "SendMatchStateCode(): START");

        if (m_currentMatch == null) {
            LogUtil.PrintWarning(GetType(), "SendMatchStateCode(): Cannot sent code if match is NULL");
            return;
        }

        try
        {
          //try sending stateCode to ALL of the players in the match (including the sender)
            await m_socket.SendMatchStateAsync(m_currentMatch.Id, (int)stateCode, payLoad, m_playersInMatch);
            //PROBLEM: the sender (this class) doesn't receive the stateCode even if he's specified as one of the receivers via m_playersInMatch
            //This class doesn't receive any ping on OnReceiveMatchState() callback
        }
        catch (Exception exception)
        {
            LogUtil.PrintWarning(GetType(), $"SendMatchStateCode(): Error {exception}");
        }

        LogUtil.PrintInfo(GetType(), "SendMatchStateCode(): FINISH");
    }

}
ReGaSLZR commented 4 years ago

To anyone getting stuck in this:

It's not noted anywhere in the docs but it seems this overload of ISocket.SendMatchStateAsync() (with the list of recipients as a param) does not work as a broadcast to all users, unless in your server, you imported/included content for function M.match_loop (please see: the sample in https://heroiclabs.com/docs/gameplay-multiplayer-server-multiplayer/#full-example )

With the help of my team's awesome server/backend engineer, we made the SendMatchStateAsync to all connected users (including the sender) possible.

Just a note: Nakama's exposed overload of ISocket with the list of User Presences as a param

SendMatchStateAsync (string matchId, long opCode, byte[] state, IEnumerable<IUserPresence> presences = null);

does NOT work out-of-the-box as a broadcast to all connected clients.

zyro commented 4 years ago

Right now this isn't a client library limitation. Echoing relayed (non-authoritative) match messages back to the sender was not possible by design in the server.

To maintain backwards compatibility we've added the ability to echo to the sender if they're explicitly placed in the list of presences. This was added in https://github.com/heroiclabs/nakama/commit/1d2b9c984e9328fe2e7ef0d0ded1474991670a57 and will be part of the next server release. Note that the presence list works the same way it's specified right now: a way to send data to a subset of match participants - so if only the sender is in the presence list only they will get the message.

Thanks for reporting this!

ReGaSLZR commented 4 years ago

Echoing relayed (non-authoritative) match messages back to the sender was not possible by design in the server.

I see. Can I request that this be at least mentioned in the docs so that future readers won't fall for the same mistake of assuming it works outright?

This was added in heroiclabs/nakama@1d2b9c9 and will be part of the next server release.

Great to know, sir! Thank you. 🙇‍♀