RevenantX / LiteNetLib

Lite reliable UDP library for Mono and .NET
https://revenantx.github.io/LiteNetLib/index.html
MIT License
3.06k stars 495 forks source link

IOS breaks with threaded socket #513

Closed FirstGearGames closed 1 year ago

FirstGearGames commented 1 year ago

Library version: [commit id (31589e6)

Framework: [Unity]

OS: [iOS]

For whichever reason this only seems to happen in developer builds, not release. I start LNL on a thread to prevent blocking during the connect/disconnect process and on iOS the client fails silently.

The offending bit is where the 'Start' method inside NetManager.Socket creates the 'UnitySocketFix' gameObject. This fails because LNL is on a thread and GameObjects can only be made on the main thread.

I've created a solution and it's been tested by two users with success but I've not been able to validate thoroughly myself as I do not own iOS. Before I make a PR I want to be certain my changes make sense to the LNL team.

First I noticed that UnitySocketFix was closing the connection when the application lost focus. I'm not sure if that's ideal as it can cause all sorts of problems for real-time games or applications. Instead I made a few changes to only reconnect if not connected.

        private void TryReconnect()
        {
            if (_netManager == null)
                return;
            //Was intentionally disconnected at some point.
            if (!_netManager.IsRunning)
                return;
            //Socket is still running.
            if (_netManager.SocketActive(false) || _netManager.SocketActive(true))
                return;

            //Socket isn't running but should be. Try to start again.
            if (!_netManager.Start(_ipv4, _ipv6, _port, _manualMode))
            {
                NetDebug.WriteError($"[S] Cannot restore connection. Ipv4 {_ipv4}, Ipv6 {_ipv6}, Port {_port}, ManualMode {_manualMode}");
                _netManager.CloseSocket(false);
            }
        }
        public bool SocketActive(bool ipv4)
        {
            if (ipv4)
            {
                if (_udpSocketv4 != null)
                    return _udpSocketv4.Connected;
                return false;
            }
            else
            {
                if (_udpSocketv6 != null)
                    return _udpSocketv6.Connected;
                return false;
            }
        }

My new implementation resides in a class that does not derive from MonoBehaviour, therefor no reason to use Update to destroy itself as it will leave memory when it's references are lost.

I also added this to the constructor/deconstructor in place of the OnApplicationPaused event. I wasn't able to test this part but the idea is to use FocusChanged to attempt a reconnect when needed.

        public PausedSocketFix() 
        {
            UnityEngine.Application.focusChanged += Application_focusChanged;
        }
                ~PausedSocketFix()
        {
            UnityEngine.Application.focusChanged -= Application_focusChanged;
        }
    private void Application_focusChanged(bool focused)
        {
            ApplicationFocused = focused;
            //If coming back into focus see if a reconnect is needed.
            if (focused)
                TryReconnect();
        }

Very last I removed the UnitySocketFix and all parts used by it within NetManager.Socket. Where the UnitySocketFix was previously initialized in the 'Start' method I just did something like this...

private PausedSocketFix _pausedSocketFix;

//In Start(IPAddress, IPAddress, int, bool)
if (_pausedSocketFix == null)
   _pausedSocketFix = new PausedSocketFix(ipv4, ipv6, port, manual);

If all of this makes sense let me know and I can definitely get the PR in.

FirstGearGames commented 1 year ago

I've had several more individuals test with success. Even while minimizing their apps/putting them in the background connections held and/or were re-established when connections dropped. I made a PR here https://github.com/RevenantX/LiteNetLib/pull/515