// 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 ProcessClientPackets() { IgnoranceIncomingPacket incomingPacket; IgnoranceClientStats clientStats; Packet payload; // Handle connection events. while (Client.ConnectionEvents.TryDequeue(out IgnoranceConnectionEvent connectionEvent)) { if (LogType == IgnoranceLogType.Verbose) { Debug.Log($"Ignorance Client Debug: Processing a client ConnectionEvents queue item. Type: {connectionEvent.EventType.ToString("{0:X2}")}"); } switch (connectionEvent.EventType) { case 0x00: // Connected to server. ClientState = ConnectionState.Connected; if (LogType != IgnoranceLogType.Nothing) { Debug.Log($"Ignorance Client has successfully connected to server at {connectionEvent.IP}:{connectionEvent.Port}"); } ignoreDataPackets = false; OnClientConnected?.Invoke(); break; case 0x01: // Disconnected from server. ClientState = ConnectionState.Disconnected; if (LogType != IgnoranceLogType.Nothing) { Debug.Log($"Ignorance Client has been disconnected from server."); } ignoreDataPackets = true; OnClientDisconnected?.Invoke(); break; default: // Unknown type. if (LogType != IgnoranceLogType.Nothing) { Debug.LogWarning($"Ignorance Client: Unknown connection event type {connectionEvent.EventType.ToString("{0:X2}")}."); } break; } } // Handle the incoming messages. while (Client.Incoming.TryDequeue(out incomingPacket)) { // Temporary fix: if ENet thread is too fast for Mirror, then ignore the packet. // This is seen sometimes if you stop the client and there's still stuff in the queue. if (ignoreDataPackets) { if (LogType == IgnoranceLogType.Verbose) { Debug.Log("Ignorance Client: ProcessClientPackets cycle skipped; ignoring data packet"); } break; } // Otherwise client recieved data, advise Mirror. // print($"Byte array: {incomingPacket.RentedByteArray.Length}. Packet Length: {incomingPacket.Length}"); payload = incomingPacket.Payload; int length = payload.Length; ArraySegment <byte> dataSegment; // Copy to working buffer and dispose of it. if (length > InternalPacketBuffer.Length) { // Unity's favourite: A fresh 'n' tasty GC Allocation! byte[] oneFreshNTastyGcAlloc = new byte[length]; payload.CopyTo(oneFreshNTastyGcAlloc); dataSegment = new ArraySegment <byte>(oneFreshNTastyGcAlloc, 0, length); } else { payload.CopyTo(InternalPacketBuffer); dataSegment = new ArraySegment <byte>(InternalPacketBuffer, 0, length); } payload.Dispose(); OnClientDataReceived?.Invoke(dataSegment, incomingPacket.Channel); #if !MIRROR_41_0_OR_NEWER // Some messages can disable the transport // If the transport was disabled by any of the messages, we have to break out of the loop and wait until we've been re-enabled. if (!enabled) { break; } #endif } // Step 3: Handle status updates. if (Client.StatusUpdates.TryDequeue(out clientStats)) { ClientStatistics = clientStats; } }
private void ProcessClientPackets() { Packet payload; // Handle connection events. while (Client.ConnectionEvents.TryDequeue(out IgnoranceConnectionEvent connectionEvent)) { if (connectionEvent.WasDisconnect) { // Disconnected from server. ClientState = ConnectionState.Disconnected; ignoreDataPackets = true; OnClientDisconnected?.Invoke(); } else { // Connected to server. ClientState = ConnectionState.Connected; ignoreDataPackets = false; OnClientConnected?.Invoke(); } } // Now handle the incoming messages. while (Client.Incoming.TryDequeue(out IgnoranceIncomingPacket incomingPacket)) { // Temporary fix: if ENet thread is too fast for Mirror, then ignore the packet. // This is seen sometimes if you stop the client and there's still stuff in the queue. if (ignoreDataPackets) { break; } // Otherwise client recieved data, advise Mirror. // print($"Byte array: {incomingPacket.RentedByteArray.Length}. Packet Length: {incomingPacket.Length}"); payload = incomingPacket.Payload; int length = payload.Length; ArraySegment <byte> dataSegment; // Copy to working buffer and dispose of it. if (length > InternalPacketBuffer.Length) { // Unity's favourite: A fresh 'n' tasty GC Allocation! byte[] oneFreshNTastyGcAlloc = new byte[length]; payload.CopyTo(oneFreshNTastyGcAlloc); dataSegment = new ArraySegment <byte>(oneFreshNTastyGcAlloc, 0, length); } else { payload.CopyTo(InternalPacketBuffer); dataSegment = new ArraySegment <byte>(InternalPacketBuffer, 0, length); } payload.Dispose(); OnClientDataReceived?.Invoke(dataSegment, incomingPacket.Channel); } // Step 3: Handle other commands. while (Client.Commands.TryDequeue(out IgnoranceCommandPacket commandPacket)) { switch (commandPacket.Type) { // ... default: break; } } // Step 4: Handle status updates. if (Client.StatusUpdates.TryDequeue(out IgnoranceClientStats clientStats)) { ClientStatistics = clientStats; } }
// 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 ProcessClientPackets() { IgnoranceIncomingPacket incomingPacket; IgnoranceCommandPacket commandPacket; IgnoranceClientStats clientStats; Packet payload; while (Client.Incoming.TryDequeue(out incomingPacket)) { // Temporary fix: if ENet thread is too fast for Mirror, then ignore the packet. // This is seen sometimes if you stop the client and there's still stuff in the queue. if (ignoreDataPackets) { if (LogType == IgnoranceLogType.Verbose) { Debug.Log("ProcessClientPackets cycle skipped; ignoring data packet"); } break; } // Otherwise client recieved data, advise Mirror. // print($"Byte array: {incomingPacket.RentedByteArray.Length}. Packet Length: {incomingPacket.Length}"); payload = incomingPacket.Payload; int length = payload.Length; ArraySegment <byte> dataSegment; // Copy to working buffer and dispose of it. if (length > InternalPacketBuffer.Length) { byte[] oneFreshNTastyGcAlloc = new byte[length]; payload.CopyTo(oneFreshNTastyGcAlloc); dataSegment = new ArraySegment <byte>(oneFreshNTastyGcAlloc, 0, length); } else { payload.CopyTo(InternalPacketBuffer); dataSegment = new ArraySegment <byte>(InternalPacketBuffer, 0, length); } payload.Dispose(); OnClientDataReceived?.Invoke(dataSegment, incomingPacket.Channel); // Some messages can disable the transport // If the transport was disabled by any of the messages, we have to break out of the loop and wait until we've been re-enabled. if (!enabled) { break; } } // Step 3: Handle other commands. while (Client.Commands.TryDequeue(out commandPacket)) { switch (commandPacket.Type) { // ... default: break; } } // Step 4: Handle status updates. if (Client.StatusUpdates.TryDequeue(out clientStats)) { ClientStatistics = clientStats; } }