0
\$\begingroup\$

I am trying to build in unity a basic logon screen. Now I'm building on something if the connection will have delay or so... There will be a dialog, where the progress of connection to server will be written... Like Attempt 1/3, Attempt 2/3...

I have Dialog class, which is building a Dialog window from prefab:

    public class Dialog
    {
        private const string TitleComponent = "Title";
        private const string ContentComponent = "Content";

        private readonly IPrefabFactory _dialogFactory = new DialogFactory(false);
        private readonly GameObject _dialog;

        public event ClickHandler Click;

        public string Title
        {
            set => GetTextComponent(TitleComponent).text = value;
        }

        public string Content
        {
            set => GetTextComponent(ContentComponent).text = value;
        }

        public Vector3 Position
        {
            set
            {
                _dialog.transform.position = value;
                _dialog.transform.localPosition = value;
            }
            get => _dialog.transform.position;
        }

        public bool Active
        {
            set => _dialog.SetActive(value);
            get => _dialog.activeSelf;
        }

        public Dialog(string name)
        {
            _dialog = _dialogFactory.Create();
            _dialog.name = name;
            _dialog.GetComponent<DialogBehaviour>().Dialog = this;
            SetToCanvas();
        }

        public void OnClick()
        {
            Click?.Invoke();
        }
        
        private void SetToCanvas()
        {
            var uiCanvas = Object.FindObjectOfType<Canvas>();
            _dialog.transform.SetParent(uiCanvas.transform, false);
            Position = new Vector3(0, 0, 0);
        }

        private TextMeshProUGUI GetTextComponent(string name)
        {
            foreach (Transform child in _dialog.transform)
            {
                if (child.name == name)
                {
                    return child.gameObject.GetComponent<TextMeshProUGUI>();
                }
            }
            
            throw new Exception($"Component with name `{name}` was not found.");
        }
    }

And I have some server "Manager", where the connection process is running.

        private void Connect()
        {
            Dialog.Active = true;
            var attempts = 1;
            do
            {
                Dialog.Content = $"Connecting to server... ({attempts++}/{MaxAttempts})";
                try
                {
                    if (Client.CurrentState == ComponentState.Ready)
                    {
                        Client.Connect();
                    }
                    else
                    {
                        Client.Reconnect();
                    }
                }
                catch (ConnectionException)
                {
                    attempts++;
                }
            } while (!Client.IsConnected && attempts <= MaxAttempts);

            if (!Client.IsConnected)
            {
                Dialog.Content = "Connection with server could not be established.";
                throw new ConnectionException("");
            }

            Dialog.Active = false;
        }

        private void CheckConnection()
        {
            if (Client.CurrentState == ComponentState.Ready)
            {
                Connect();
            }
            else
            {
                if (Client.CheckConnection()) return;
                Connect();
            }
        }

        protected Message SendAndReceive(Message message)
        {
            CheckConnection();
            Message response = null;
            
            try
            {
                response = Client.Send(message);
            }
            catch (MessageSendException)
            {
                Dialog.Active = true;
                var attempts = 2;
                do
                {
                    Dialog.Content = $"Sending message... ({attempts}/{MaxAttempts})";
                    try
                    {
                        response = Client.TryReceive();
                    }
                    catch (MessageSendException)
                    {
                        attempts++;
                    }
                } while (response == null && attempts <= MaxAttempts);
            }

            if (response == null)
            {
                Dialog.Content = "Server did not send any response. Probably connection is lost.";
                throw new MessageSendException("");
            }

            Dialog.Active = false;

            return response;
        }

I want to provide some output to screen about whats hapening in that dialog class. But, where is the problem. Everything is so much fast, that the dialog can't even render on the start and during all attempts to connect to server, the main thread is just stucked to the time, when the connection ends.

I was finding something about how to make this happen like in another thread or so... Or find coroutines, but I have no idea how to make this work...

It should work like this:

  • Render dialog.
  • Write that the connection attempt is running.
  • During another attempts update the status.
  • If successful dismiss dialog.

After all this happen, something similar should happen with the sending. How to make this working without stucking the main thread and make all steps run smoothly one after another?

\$\endgroup\$
1
\$\begingroup\$

You didn't post any of the code which actually sends the login attempt to the server, as all of that is abstracted away in an object "Client". And there are quite a lot of ways to do networking in Unity. But most network APIs don't block the current thread until there is an answer. They usually send a message and then return immediately. The actual response is then processed by a handler function when it finally arrives.

You are already using that pattern for the click events for your button. Your networking API likely uses a similar pattern for asynchronous operations.

So the usual pattern you would use is:

  1. Send a request using an asynchronous networking API which returns immediately. Such APIs will usually require a callback function which is to be called when there is a response, and often a second one to be called in case of an error.
  2. Keep running updates and keep rendering the dialog (which Unity does automatically for you). Perhaps showing some animated indicator that there is a pending connection attempt.
  3. When a response from the server arrives, or the network API reports an error, it executes a callback function.
  4. That callback function notifies the Dialog of the result of the network operation
  5. During the next Update() of the Dialog, it notices that there is a notification from the network API and changes the UI accordingly
\$\endgroup\$
3
  • \$\begingroup\$ Hah, well, got this technique first, but then I added an synchronous send, which is waiting for message... Ok, That sounds good, I will use asynchronous sending and review my asynchronous connection method, which will replace the synchronous one. \$\endgroup\$ – Zechy Jul 21 '20 at 11:44
  • \$\begingroup\$ @Zechy synchronous APIs might seem simpler at first, but they really have no place in any application with a graphical user interface. Sure, you can use threads to build an asynchronous API out of a synchronous one, but that way you just end up reinventing the asynchronous API which usually already exists. \$\endgroup\$ – Philipp Jul 21 '20 at 11:50
  • \$\begingroup\$ Yep... I see... :) I have it asynchronous from the first time, but after some code written on server side I though it would be good to add also synchronous send and receive... As you say, for the GUI nothing good. \$\endgroup\$ – Zechy Jul 21 '20 at 11:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.