Esempio n. 1
0
        // This runs in a seperate thread, be careful accessing anything outside of it's thread
        // or you may get an AccessViolation/crash.
        private void ThreadWorker(Object parameters)
        {
            if (Verbosity > 0)
            {
                Debug.Log("Ignorance Client: Initializing. Please stand by...");
            }

            ThreadParamInfo      setupInfo;
            Address              clientAddress = new Address();
            Peer                 clientPeer;  // The peer object that represents the client's connection.
            Host                 clientHost;  // NOT related to Mirror "Client Host". This is the client's ENet Host Object.
            Event                clientEvent; // Used when clients get events on the network.
            IgnoranceClientStats icsu = default;
            bool                 alreadyNotifiedAboutDisconnect = false;

            // Grab the setup information.
            if (parameters.GetType() == typeof(ThreadParamInfo))
            {
                setupInfo = (ThreadParamInfo)parameters;
            }
            else
            {
                Debug.LogError("Ignorance Client: Startup failure; Invalid thread parameters. Aborting.");
                return;
            }

            // Attempt to initialize ENet inside the thread.
            if (Library.Initialize())
            {
                Debug.Log("Ignorance Client: ENet Native successfully initialized.");
            }
            else
            {
                Debug.LogError("Ignorance Client: Failed to initialize ENet Native. Aborting.");
                return;
            }

            // Attempt to connect to our target.
            clientAddress.SetHost(setupInfo.Address);
            clientAddress.Port = (ushort)setupInfo.Port;

            using (clientHost = new Host())
            {
                try
                {
                    clientHost.Create();
                    clientPeer = clientHost.Connect(clientAddress, setupInfo.Channels);
                }
                catch (Exception ex)
                {
                    // Oops, something failed.
                    Debug.LogError($"Ignorance Client: Looks like something went wrong. While attempting to create client object, we caught an exception:\n{ex.Message}");
                    Debug.LogError($"You could try the debug-enabled version of the native ENet library which creates a logfile, or alternatively you could try restart " +
                                   $"your device to ensure jank is cleared out of memory. If problems persist, please file a support ticket explaining what happened.");

                    Library.Deinitialize();
                    return;
                }

                // Process network events as long as we're not ceasing operation.
                while (!CeaseOperation)
                {
                    bool pollComplete = false;

                    while (Commands.TryDequeue(out IgnoranceCommandPacket ignoranceCommandPacket))
                    {
                        switch (ignoranceCommandPacket.Type)
                        {
                        case IgnoranceCommandType.ClientStatusRequest:
                            // Respond with statistics so far.
                            if (!clientPeer.IsSet)
                            {
                                break;
                            }

                            icsu.RTT = clientPeer.RoundTripTime;

                            icsu.BytesReceived = clientPeer.BytesReceived;
                            icsu.BytesSent     = clientPeer.BytesSent;

                            icsu.PacketsReceived = clientHost.PacketsReceived;
                            icsu.PacketsSent     = clientPeer.PacketsSent;
                            icsu.PacketsLost     = clientPeer.PacketsLost;

                            StatusUpdates.Enqueue(icsu);
                            break;

                        case IgnoranceCommandType.ClientWantsToStop:
                            CeaseOperation = true;
                            break;
                        }
                    }

                    // If something outside the thread has told us to stop execution, then we need to break out of this while loop.
                    if (CeaseOperation)
                    {
                        break;
                    }

                    // Step 1: Sending to Server
                    while (Outgoing.TryDequeue(out IgnoranceOutgoingPacket outgoingPacket))
                    {
                        // TODO: Revise this, could we tell the Peer to disconnect right here?
                        // Stop early if we get a client stop packet.
                        // if (outgoingPacket.Type == IgnorancePacketType.ClientWantsToStop) break;

                        int ret = clientPeer.Send(outgoingPacket.Channel, ref outgoingPacket.Payload);

                        if (ret < 0 && setupInfo.Verbosity > 0)
                        {
                            Debug.LogWarning($"Ignorance Client: ENet error {ret} while sending packet to Server via Peer {outgoingPacket.NativePeerId}.");
                        }
                    }

                    // If something outside the thread has told us to stop execution, then we need to break out of this while loop.
                    // while loop to break out of is while(!CeaseOperation).
                    if (CeaseOperation)
                    {
                        break;
                    }

                    // Step 2: Receive Data packets
                    // This loops until polling is completed. It may take a while, if it's
                    // a slow networking day.
                    while (!pollComplete)
                    {
                        Packet incomingPacket;
                        Peer   incomingPeer;
                        int    incomingPacketLength;

                        // Any events worth checking out?
                        if (clientHost.CheckEvents(out clientEvent) <= 0)
                        {
                            // If service time is met, break out of it.
                            if (clientHost.Service(setupInfo.PollTime, out clientEvent) <= 0)
                            {
                                break;
                            }

                            // Poll is done.
                            pollComplete = true;
                        }

                        // Setup the packet references.
                        incomingPeer = clientEvent.Peer;

                        // Now, let's handle those events.
                        switch (clientEvent.Type)
                        {
                        case EventType.None:
                        default:
                            break;

                        case EventType.Connect:
                            if (setupInfo.Verbosity > 0)
                            {
                                Debug.Log("Ignorance Client: ENet has connected to the server.");
                            }

                            ConnectionEvents.Enqueue(new IgnoranceConnectionEvent
                            {
                                EventType    = 0x00,
                                NativePeerId = incomingPeer.ID,
                                IP           = incomingPeer.IP,
                                Port         = incomingPeer.Port
                            });
                            break;

                        case EventType.Disconnect:
                        case EventType.Timeout:
                            if (setupInfo.Verbosity > 0)
                            {
                                Debug.Log("Ignorance Client: ENet has been disconnected from the server.");
                            }

                            ConnectionEvents.Enqueue(new IgnoranceConnectionEvent {
                                EventType = 0x01
                            });
                            CeaseOperation = true;
                            alreadyNotifiedAboutDisconnect = true;
                            break;

                        case EventType.Receive:
                            // Receive event type usually includes a packet; so cache its reference.
                            incomingPacket = clientEvent.Packet;

                            if (!incomingPacket.IsSet)
                            {
                                if (setupInfo.Verbosity > 0)
                                {
                                    Debug.LogWarning($"Ignorance Client: A receive event did not supply us with a packet to work with. This should never happen.");
                                }
                                break;
                            }

                            incomingPacketLength = incomingPacket.Length;

                            // Never consume more than we can have capacity for.
                            if (incomingPacketLength > setupInfo.PacketSizeLimit)
                            {
                                if (setupInfo.Verbosity > 0)
                                {
                                    Debug.LogWarning($"Ignorance Client: Incoming packet is too big. My limit is {setupInfo.PacketSizeLimit} byte(s) whilest this packet is {incomingPacketLength} bytes.");
                                }

                                incomingPacket.Dispose();
                                break;
                            }

                            IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket
                            {
                                Channel      = clientEvent.ChannelID,
                                NativePeerId = incomingPeer.ID,
                                Payload      = incomingPacket
                            };

                            Incoming.Enqueue(incomingQueuePacket);
                            break;
                        }
                    }

                    // If something outside the thread has told us to stop execution, then we need to break out of this while loop.
                    // while loop to break out of is while(!CeaseOperation).
                    if (CeaseOperation)
                    {
                        break;
                    }
                }

                Debug.Log("Ignorance Client: Thread shutdown commencing. Disconnecting and flushing connection.");

                // Flush the client and disconnect.
                clientPeer.Disconnect(0);
                clientHost.Flush();

                // Fix for client stuck in limbo, since the disconnection event may not be fired until next loop.
                if (!alreadyNotifiedAboutDisconnect)
                {
                    ConnectionEvents.Enqueue(new IgnoranceConnectionEvent {
                        EventType = 0x01
                    });
                    alreadyNotifiedAboutDisconnect = true;
                }
            }

            // Fix for client stuck in limbo, since the disconnection event may not be fired until next loop, again.
            if (!alreadyNotifiedAboutDisconnect)
            {
                ConnectionEvents.Enqueue(new IgnoranceConnectionEvent {
                    EventType = 0x01
                });
            }

            // Deinitialize
            Library.Deinitialize();

            if (setupInfo.Verbosity > 0)
            {
                Debug.Log("Ignorance Client: Shutdown complete.");
            }
        }
Esempio n. 2
0
        private void ThreadWorker(Object parameters)
        {
            if (Verbosity > 0)
            {
                Debug.Log("Ignorance Server: Initializing. Please stand by...");
            }

            // Thread cache items
            ThreadParamInfo setupInfo;
            Address         serverAddress = new Address();
            Host            serverENetHost;
            Event           serverENetEvent;

            Peer[] serverPeerArray;
            IgnoranceClientStats peerStats = default;

            // Grab the setup information.
            if (parameters.GetType() == typeof(ThreadParamInfo))
            {
                setupInfo = (ThreadParamInfo)parameters;
            }
            else
            {
                Debug.LogError("Ignorance Server: Startup failure; Invalid thread parameters. Aborting.");
                return;
            }

            // Attempt to initialize ENet inside the thread.
            if (Library.Initialize())
            {
                Debug.Log("Ignorance Server: ENet Native successfully initialized.");
            }
            else
            {
                Debug.LogError("Ignorance Server: Failed to initialize ENet Native. Aborting.");
                return;
            }

            // Configure the server address.
            serverAddress.SetHost(setupInfo.Address);
            serverAddress.Port = (ushort)setupInfo.Port;
            serverPeerArray    = new Peer[setupInfo.Peers];

            using (serverENetHost = new Host())
            {
                // Create the server object.
                try
                {
                    serverENetHost.Create(serverAddress, setupInfo.Peers, setupInfo.Channels);
                }
                catch (Exception ex)
                {
                    Debug.LogError($"Ignorance Server: While attempting to create server host object, we caught an exception:\n{ex.Message}");
                    Debug.LogError($"If you are getting a \"Host creation call failed\" exception, please ensure you don't have a server already running on the same IP and Port.\n" +
                                   $"Multiple server instances running on the same port are not supported. Also check to see if ports are not in-use by another application. In the worse case scenario, " +
                                   $"restart your device to ensure no random background ENet threads are active that haven't been cleaned up correctly. If problems persist, please file a support ticket.");

                    Library.Deinitialize();
                    return;
                }

                // Loop until we're told to cease operations.
                while (!CeaseOperation)
                {
                    // Intermission: Command Handling
                    while (Commands.TryDequeue(out IgnoranceCommandPacket commandPacket))
                    {
                        switch (commandPacket.Type)
                        {
                        default:
                            break;

                        // Boot a Peer off the Server.
                        case IgnoranceCommandType.ServerKickPeer:
                            uint targetPeer = commandPacket.PeerId;

                            if (!serverPeerArray[targetPeer].IsSet)
                            {
                                continue;
                            }

                            if (setupInfo.Verbosity > 0)
                            {
                                Debug.Log($"Ignorance Server: Booting Peer {targetPeer} off");
                            }

                            IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent
                            {
                                EventType    = 0x01,
                                NativePeerId = targetPeer
                            };

                            DisconnectionEvents.Enqueue(iced);

                            // Disconnect and reset the peer array's entry for that peer.
                            serverPeerArray[targetPeer].DisconnectNow(0);
                            serverPeerArray[targetPeer] = default;
                            break;

                        case IgnoranceCommandType.ServerStatusRequest:
                            IgnoranceServerStats serverStats;
                            if (!RecycledServerStatBlocks.TryDequeue(out serverStats))
                            {
                                serverStats.PeerStats = new Dictionary <int, IgnoranceClientStats>(setupInfo.Peers);
                            }

                            serverStats.PeerStats.Clear();

                            serverStats.BytesReceived = serverENetHost.BytesReceived;
                            serverStats.BytesSent     = serverENetHost.BytesSent;

                            serverStats.PacketsReceived = serverENetHost.PacketsReceived;
                            serverStats.PacketsSent     = serverENetHost.PacketsSent;

                            serverStats.PeersCount = serverENetHost.PeersCount;

                            for (int i = 0; i < serverPeerArray.Length; i++)
                            {
                                if (!serverPeerArray[i].IsSet)
                                {
                                    continue;
                                }

                                peerStats.RTT = serverPeerArray[i].RoundTripTime;

                                peerStats.BytesReceived = serverPeerArray[i].BytesReceived;
                                peerStats.BytesSent     = serverPeerArray[i].BytesSent;

                                peerStats.PacketsSent = serverPeerArray[i].PacketsSent;
                                peerStats.PacketsLost = serverPeerArray[i].PacketsLost;

                                serverStats.PeerStats.Add(i, peerStats);
                            }

                            StatusUpdates.Enqueue(serverStats);
                            break;
                        }
                    }

                    // Step One:
                    // ---> Sending to peers
                    while (Outgoing.TryDequeue(out IgnoranceOutgoingPacket outgoingPacket))
                    {
                        // Only create a packet if the server knows the peer.
                        if (serverPeerArray[outgoingPacket.NativePeerId].IsSet)
                        {
                            int ret = serverPeerArray[outgoingPacket.NativePeerId].Send(outgoingPacket.Channel, ref outgoingPacket.Payload);

                            if (ret < 0 && setupInfo.Verbosity > 0)
                            {
                                Debug.LogWarning($"Ignorance Server: ENet error {ret} while sending packet to Peer {outgoingPacket.NativePeerId}.");
                            }
                        }
                        else
                        {
                            // A peer might have disconnected, this is OK - just log the packet if set to paranoid.
                            if (setupInfo.Verbosity > 1)
                            {
                                Debug.LogWarning("Ignorance Server: Can't send packet, a native peer object is not set. This may be normal if the Peer has disconnected before this send cycle.");
                            }
                        }
                    }

                    // Step 2
                    // <--- Receiving from peers
                    bool pollComplete = false;

                    while (!pollComplete)
                    {
                        Packet incomingPacket;
                        Peer   incomingPeer;
                        int    incomingPacketLength;

                        // Any events happening?
                        if (serverENetHost.CheckEvents(out serverENetEvent) <= 0)
                        {
                            // If service time is met, break out of it.
                            if (serverENetHost.Service(setupInfo.PollTime, out serverENetEvent) <= 0)
                            {
                                break;
                            }

                            pollComplete = true;
                        }

                        // Setup the packet references.
                        incomingPeer = serverENetEvent.Peer;

                        // What type are you?
                        switch (serverENetEvent.Type)
                        {
                        // Idle.
                        case EventType.None:
                        default:
                            break;

                        // Connection Event.
                        case EventType.Connect:
                            if (setupInfo.Verbosity > 1)
                            {
                                Debug.Log($"Ignorance Server: Peer ID {incomingPeer.ID} says Hi.");
                            }

                            IgnoranceConnectionEvent ice = new IgnoranceConnectionEvent()
                            {
                                NativePeerId = incomingPeer.ID,
                                IP           = incomingPeer.IP,
                                Port         = incomingPeer.Port
                            };

                            ConnectionEvents.Enqueue(ice);

                            // Assign a reference to the Peer.
                            serverPeerArray[incomingPeer.ID] = incomingPeer;
                            break;

                        // Disconnect/Timeout. Mirror doesn't care if it's either, so we lump them together.
                        case EventType.Disconnect:
                        case EventType.Timeout:
                            if (!serverPeerArray[incomingPeer.ID].IsSet)
                            {
                                break;
                            }

                            if (setupInfo.Verbosity > 1)
                            {
                                Debug.Log($"Ignorance Server: Peer {incomingPeer.ID} has disconnected.");
                            }

                            IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent
                            {
                                EventType    = 0x01,
                                NativePeerId = incomingPeer.ID
                            };

                            DisconnectionEvents.Enqueue(iced);

                            // Reset the peer array's entry for that peer.
                            serverPeerArray[incomingPeer.ID] = default;
                            break;

                        case EventType.Receive:
                            // Receive event type usually includes a packet; so cache its reference.
                            incomingPacket = serverENetEvent.Packet;
                            if (!incomingPacket.IsSet)
                            {
                                if (setupInfo.Verbosity > 0)
                                {
                                    Debug.LogWarning($"Ignorance Server: A receive event did not supply us with a packet to work with. This should never happen.");
                                }
                                break;
                            }

                            incomingPacketLength = incomingPacket.Length;

                            // Firstly check if the packet is too big. If it is, do not process it - drop it.
                            if (incomingPacketLength > setupInfo.PacketSizeLimit)
                            {
                                if (setupInfo.Verbosity > 0)
                                {
                                    Debug.LogWarning($"Ignorance Server: Incoming packet is too big. My limit is {setupInfo.PacketSizeLimit} byte(s) whilest this packet is {incomingPacketLength} bytes.");
                                }

                                incomingPacket.Dispose();
                                break;
                            }

                            IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket
                            {
                                Channel      = serverENetEvent.ChannelID,
                                NativePeerId = incomingPeer.ID,
                                Payload      = incomingPacket,
                            };

                            // Enqueue.
                            Incoming.Enqueue(incomingQueuePacket);
                            break;
                        }
                    }
                }

                if (Verbosity > 0)
                {
                    Debug.Log("Ignorance Server: Thread shutdown commencing. Flushing connections.");
                }

                // Cleanup and flush everything.
                serverENetHost.Flush();

                // Kick everyone.
                for (int i = 0; i < serverPeerArray.Length; i++)
                {
                    if (!serverPeerArray[i].IsSet)
                    {
                        continue;
                    }
                    serverPeerArray[i].DisconnectNow(0);
                }
            }

            if (setupInfo.Verbosity > 0)
            {
                Debug.Log("Ignorance Server: Shutdown complete.");
            }

            Library.Deinitialize();
        }