// 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."); } }
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(); }