// 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; // Unused for now // bool emergencyStop = 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. This threads' f****d."); return; } // Attempt to connect to our target. clientAddress.SetHost(setupInfo.Address); clientAddress.Port = (ushort)setupInfo.Port; using (clientHost = new Host()) { // TODO: Maybe try catch this clientHost.Create(); clientPeer = clientHost.Connect(clientAddress, setupInfo.Channels); while (Commands.TryDequeue(out IgnoranceCommandPacket commandPacket)) { switch (commandPacket.Type) { default: break; case IgnoranceCommandType.ClientWantsToStop: CeaseOperation = true; break; 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; } } // Process network events as long as we're not ceasing operation. while (!CeaseOperation) { bool pollComplete = false; // 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) { // Thread cache items ThreadParamInfo setupInfo; Address serverAddress = new Address(); Host serverENetHost; Event serverENetEvent; Peer[] serverPeerArray; // Grab the setup information. if (parameters.GetType() == typeof(ThreadParamInfo)) { setupInfo = (ThreadParamInfo)parameters; } else { return; } // Attempt to initialize ENet inside the thread. if (Library.Initialize()) { } else { 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. serverENetHost.Create(serverAddress, setupInfo.Peers, setupInfo.Channels); // 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; } IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent() { WasDisconnect = true, NativePeerId = targetPeer }; DisconnectionEvents.Enqueue(iced); // Disconnect and reset the peer array's entry for that peer. serverPeerArray[targetPeer].DisconnectNow(0); serverPeerArray[targetPeer] = default; 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); } else { // A peer might have disconnected, this is OK - just log the packet if set to paranoid. } } // 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; switch (serverENetEvent.Type) { // Idle. case EventType.None: default: break; // Connection Event. case EventType.Connect: 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; } IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent() { WasDisconnect = true, 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) { 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) { incomingPacket.Dispose(); break; } IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket { Channel = serverENetEvent.ChannelID, NativePeerId = incomingPeer.ID, Payload = incomingPacket, }; // Enqueue. Incoming.Enqueue(incomingQueuePacket); break; } } } // 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); } } // Flush again to ensure ENet gets those Disconnection stuff out. // May not be needed; better to err on side of caution Library.Deinitialize(); }
// 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("Client Worker Thread: Startup"); } ThreadParamInfo setupInfo; Address clientAddress = new Address(); Peer clientPeer; Host clientENetHost; Event clientENetEvent; IgnoranceClientStats icsu = default; // Grab the setup information. if (parameters.GetType() == typeof(ThreadParamInfo)) { setupInfo = (ThreadParamInfo)parameters; } else { Debug.LogError("Thread worker startup failure: Invalid thread parameters. Aborting."); return; } // Attempt to initialize ENet inside the thread. if (Library.Initialize()) { Debug.Log("Client Worker Thread: Initialized ENet."); } else { Debug.LogError("Client Worker Thread: Failed to initialize ENet. This threads' f****d."); return; } // Attempt to connect to our target. clientAddress.SetHost(setupInfo.Address); clientAddress.Port = (ushort)setupInfo.Port; using (clientENetHost = new Host()) { // TODO: Maybe try catch this clientENetHost.Create(); clientPeer = clientENetHost.Connect(clientAddress, setupInfo.Channels); while (!CeaseOperation) { bool pollComplete = false; // Step 0: Handle commands. while (Commands.TryDequeue(out IgnoranceCommandPacket commandPacket)) { switch (commandPacket.Type) { default: break; case IgnoranceCommandType.ClientWantsToStop: CeaseOperation = true; break; case IgnoranceCommandType.ClientRequestsStatusUpdate: // Respond with statistics so far. if (!clientPeer.IsSet) { break; } icsu.RTT = clientPeer.RoundTripTime; icsu.BytesReceived = clientPeer.BytesReceived; icsu.BytesSent = clientPeer.BytesSent; icsu.PacketsReceived = clientENetHost.PacketsReceived; icsu.PacketsSent = clientPeer.PacketsSent; icsu.PacketsLost = clientPeer.PacketsLost; StatusUpdates.Enqueue(icsu); break; } } // Step 1: Send out data. // ---> 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($"Client Worker Thread: Failed sending a packet to Peer {outgoingPacket.NativePeerId}, error code {ret}"); } } // 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 (clientENetHost.CheckEvents(out clientENetEvent) <= 0) { // If service time is met, break out of it. if (clientENetHost.Service(setupInfo.PollTime, out clientENetEvent) <= 0) { break; } // Poll is done. pollComplete = true; } // Setup the packet references. incomingPeer = clientENetEvent.Peer; // Now, let's handle those events. switch (clientENetEvent.Type) { case EventType.None: default: break; case EventType.Connect: ConnectionEvents.Enqueue(new IgnoranceConnectionEvent() { NativePeerId = incomingPeer.ID, IP = incomingPeer.IP, Port = incomingPeer.Port }); break; case EventType.Disconnect: case EventType.Timeout: ConnectionEvents.Enqueue(new IgnoranceConnectionEvent() { WasDisconnect = true }); break; case EventType.Receive: // Receive event type usually includes a packet; so cache its reference. incomingPacket = clientENetEvent.Packet; if (!incomingPacket.IsSet) { if (setupInfo.Verbosity > 0) { Debug.LogWarning($"Client Worker Thread: 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($"Client Worker Thread: Received a packet too large, {incomingPacketLength} bytes while our limit is {setupInfo.PacketSizeLimit} bytes."); } incomingPacket.Dispose(); break; } IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket { Channel = clientENetEvent.ChannelID, NativePeerId = incomingPeer.ID, Payload = incomingPacket }; Incoming.Enqueue(incomingQueuePacket); break; } } } // Flush the client and disconnect. clientPeer.Disconnect(0); clientENetHost.Flush(); } // Deinitialize Library.Deinitialize(); if (setupInfo.Verbosity > 0) { Debug.Log("Client Worker Thread: Shutdown."); } }
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; // 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. This threads' f****d."); 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. serverENetHost.Create(serverAddress, setupInfo.Peers, setupInfo.Channels); // 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 ENet Peer {targetPeer} off this server instance."); } 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; } } // 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: Hello new peer with ID {incomingPeer.ID}!"); } 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: Bye bye Peer {incomingPeer.ID}; They have 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(); }