fishbait/ServerProject-DONT-IMPORT-INTO-UNITY/MultiCompiled/Ignorance/Ignorance.cs
2021-08-19 04:38:45 -04:00

510 lines
18 KiB
C#

// Ignorance 1.4.x
// Ignorance. It really kicks the Unity LLAPIs ass.
// https://github.com/SoftwareGuy/Ignorance
// -----------------
// Copyright (c) 2019 - 2020 Matt Coburn (SoftwareGuy/Coburn64)
// Ignorance Transport is licensed under the MIT license. Refer
// to the LICENSE file for more information.
// -----------------
// Ignorance Experimental (New) Version
// -----------------
using ENet;
using Mirror;
using System;
using System.Collections.Generic;
namespace IgnoranceTransport
{
public class Ignorance : Transport
{
public int port = 7777;
public IgnoranceLogType LogType = IgnoranceLogType.Standard;
public bool DebugDisplay = false;
public bool serverBindsAll = true;
public string serverBindAddress = string.Empty;
public int serverMaxPeerCapacity = 50;
public int serverMaxNativeWaitTime = 1;
public int clientMaxNativeWaitTime = 3;
public IgnoranceChannelTypes[] Channels = new[] { IgnoranceChannelTypes.Reliable, IgnoranceChannelTypes.Unreliable };
public int PacketBufferCapacity = 4096;
public int MaxAllowedPacketSize = 33554432;
public IgnoranceClientStats ClientStatistics;
public override bool Available()
{
return true;
}
public override void Awake()
{
if (LogType != IgnoranceLogType.Nothing)
Console.WriteLine($"Thanks for using Ignorance {IgnoranceInternals.Version}. Keep up to date, report bugs and support the developer at https://github.com/SoftwareGuy/Ignorance!");
}
public override string ToString()
{
return $"Ignorance v{IgnoranceInternals.Version}";
}
public override void ClientConnect(string address)
{
ClientState = ConnectionState.Connecting;
cachedConnectionAddress = address;
// Initialize.
InitializeClientBackend();
// Get going.
ignoreDataPackets = false;
// Start!
Client.Start();
}
public override void ClientConnect(Uri uri)
{
if (uri.Scheme != IgnoranceInternals.Scheme)
throw new ArgumentException($"You used an invalid URI: {uri}. Please use {IgnoranceInternals.Scheme}://host:port instead", nameof(uri));
if (!uri.IsDefaultPort)
// Set the communication port to the one specified.
port = uri.Port;
// Pass onwards to the proper handler.
ClientConnect(uri.Host);
}
public override bool ClientConnected() => ClientState == ConnectionState.Connected;
public override void ClientDisconnect()
{
if (Client != null)
Client.Stop();
// TODO: Figure this one out to see if it's related to a race condition.
// Maybe experiment with a while loop to pause main thread when disconnecting,
// since client might not stop on a dime.
// while(Client.IsAlive) ;
// v1.4.0b1: Probably fixed in IgnoranceClient.cs; need further testing.
// ignoreDataPackets = true;
ClientState = ConnectionState.Disconnected;
}
// v1.4.0b6: Mirror rearranged the ClientSend params, so we need to apply a fix for that or
// we end up using the obsoleted version. The obsolete version isn't a fatal error, but
// it's best to stick with the new structures.
public override void ClientSend(int channelId, ArraySegment<byte> segment)
{
if (Client == null)
{
return;
}
if (channelId < 0 || channelId > Channels.Length)
{
return;
}
// Create our struct...
Packet clientOutgoingPacket = default;
int byteCount = segment.Count;
int byteOffset = segment.Offset;
// Set our desired flags...
PacketFlags desiredFlags = (PacketFlags)Channels[channelId];
// Create the packet.
clientOutgoingPacket.Create(segment.Array, byteOffset, byteCount + byteOffset, desiredFlags);
// byteCount
// Enqueue the packet.
IgnoranceOutgoingPacket dispatchPacket = new IgnoranceOutgoingPacket
{
Channel = (byte)channelId,
Payload = clientOutgoingPacket
};
// Pass the packet onto the thread for dispatch.
Client.Outgoing.Enqueue(dispatchPacket);
}
public override bool ServerActive()
{
// Very simple check.
return Server != null && Server.IsAlive;
}
public override bool ServerDisconnect(int connectionId) => ServerDisconnectLegacy(connectionId);
public override string ServerGetClientAddress(int connectionId)
{
if (ConnectionLookupDict.TryGetValue(connectionId, out PeerConnectionData details))
return $"{details.IP}:{details.Port}";
return "(unavailable)";
}
public override void ServerSend(int connectionId, int channelId, ArraySegment<byte> segment)
{
// Debug.Log($"ServerSend({connectionId}, {channelId}, <{segment.Count} byte segment>)");
if (Server == null)
{
// Debug.LogError("Cannot enqueue data packet; our Server object is null. Something has gone wrong.");
return;
}
if (channelId < 0 || channelId > Channels.Length)
{
// Debug.LogError("Channel ID is out of bounds.");
return;
}
// Packet Struct
Packet serverOutgoingPacket = default;
int byteCount = segment.Count;
int byteOffset = segment.Offset;
PacketFlags desiredFlags = (PacketFlags)Channels[channelId];
// Create the packet.
serverOutgoingPacket.Create(segment.Array, byteOffset, byteCount + byteOffset, desiredFlags);
// Enqueue the packet.
IgnoranceOutgoingPacket dispatchPacket = new IgnoranceOutgoingPacket
{
Channel = (byte)channelId,
NativePeerId = (uint)connectionId - 1, // ENet's native peer ID will be ConnID - 1
Payload = serverOutgoingPacket
};
Server.Outgoing.Enqueue(dispatchPacket);
}
public override void ServerStart(ushort _port)
{
if (LogType != IgnoranceLogType.Nothing)
Console.WriteLine("Ignorance Server Instance starting up...");
port = _port;
InitializeServerBackend();
Server.Start();
}
public override void ServerStop()
{
if (Server != null)
{
if (LogType != IgnoranceLogType.Nothing)
Console.WriteLine("Ignorance Server Instance shutting down...");
Server.Stop();
}
ConnectionLookupDict.Clear();
}
public override Uri ServerUri()
{
UriBuilder builder = new UriBuilder
{
Scheme = IgnoranceInternals.Scheme,
Host = serverBindAddress,
Port = port
};
return builder.Uri;
}
public override void Shutdown()
{
// TODO: Nothing needed here?
}
private void InitializeServerBackend()
{
if (Server == null)
{
// Debug.LogWarning("IgnoranceServer reference for Server mode was null. This shouldn't happen, but to be safe we'll reinitialize it.");
Server = new IgnoranceServer();
}
// Set up the new IgnoranceServer reference.
if (serverBindsAll)
// MacOS is special. It's also a massive thorn in my backside.
Server.BindAddress = IgnoranceInternals.BindAllMacs;
else
// Use the supplied bind address.
Server.BindAddress = serverBindAddress;
// Sets port, maximum peers, max channels, the server poll time, maximum packet size and verbosity.
Server.BindPort = port;
Server.MaximumPeers = serverMaxPeerCapacity;
Server.MaximumChannels = Channels.Length;
Server.PollTime = serverMaxNativeWaitTime;
Server.MaximumPacketSize = MaxAllowedPacketSize;
Server.Verbosity = (int)LogType;
// Initializes the packet buffer.
// Allocates once, that's it.
if (InternalPacketBuffer == null)
InternalPacketBuffer = new byte[PacketBufferCapacity];
}
private void InitializeClientBackend()
{
if (Client == null)
{
// Debug.LogWarning("Ignorance: IgnoranceClient reference for Client mode was null. This shouldn't happen, but to be safe we'll reinitialize it.");
Client = new IgnoranceClient();
}
// Sets address, port, channels to expect, verbosity, the server poll time and maximum packet size.
Client.ConnectAddress = cachedConnectionAddress;
Client.ConnectPort = port;
Client.ExpectedChannels = Channels.Length;
Client.PollTime = clientMaxNativeWaitTime;
Client.MaximumPacketSize = MaxAllowedPacketSize;
Client.Verbosity = (int)LogType;
// Initializes the packet buffer.
// Allocates once, that's it.
if (InternalPacketBuffer == null)
InternalPacketBuffer = new byte[PacketBufferCapacity];
}
private void ProcessServerPackets()
{
IgnoranceIncomingPacket incomingPacket;
IgnoranceConnectionEvent connectionEvent;
int adjustedConnectionId;
Packet payload;
// Incoming connection events.
while (Server.ConnectionEvents.TryDequeue(out connectionEvent))
{
adjustedConnectionId = (int)connectionEvent.NativePeerId + 1;
// TODO: Investigate ArgumentException: An item with the same key has already been added. Key: <id>
ConnectionLookupDict.Add(adjustedConnectionId, new PeerConnectionData
{
NativePeerId = connectionEvent.NativePeerId,
IP = connectionEvent.IP,
Port = connectionEvent.Port
});
OnServerConnected?.Invoke(adjustedConnectionId);
}
// Handle incoming data packets.
// Console.WriteLine($"Server Incoming Queue is {Server.Incoming.Count}");
while (Server.Incoming.TryDequeue(out incomingPacket))
{
adjustedConnectionId = (int)incomingPacket.NativePeerId + 1;
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();
OnServerDataReceived?.Invoke(adjustedConnectionId, dataSegment, incomingPacket.Channel);
}
// Disconnection events.
while (Server.DisconnectionEvents.TryDequeue(out IgnoranceConnectionEvent disconnectionEvent))
{
adjustedConnectionId = (int)disconnectionEvent.NativePeerId + 1;
ConnectionLookupDict.Remove(adjustedConnectionId);
// Invoke Mirror handler.
OnServerDisconnected?.Invoke(adjustedConnectionId);
}
}
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;
}
}
// Ignorance 1.4.0b5: To use Mirror's polling or not use Mirror's polling, that is up to the developer to decide
// IMPORTANT: Set Ignorance' execution order before everything else. Yes, that's -32000 !!
// This ensures it has priority over other things.
// FixedUpdate can be called many times per frame.
// Once we've handled stuff, we set a flag so that we don't poll again for this frame.
private bool fixedUpdateCompletedWork;
public void FixedUpdate()
{
if (fixedUpdateCompletedWork) return;
ProcessAndExecuteAllPackets();
// Flip the bool to signal we've done our work.
fixedUpdateCompletedWork = true;
}
// Normally, Mirror blocks Update() due to poor design decisions...
// But thanks to Vincenzo, we've found a way to bypass that block.
// Update is called once per frame. We don't have to worry about this shit now.
public override void Update()
{
// Process what FixedUpdate missed, only if the boolean is not set.
if (!fixedUpdateCompletedWork)
ProcessAndExecuteAllPackets();
// Flip back the bool, so it can be reset.
fixedUpdateCompletedWork = false;
}
// Processes and Executes All Packets.
private void ProcessAndExecuteAllPackets()
{
// Process Server Events...
if (Server.IsAlive)
ProcessServerPackets();
// Process Client Events...
if (Client.IsAlive)
{
ProcessClientPackets();
}
}
public override int GetMaxPacketSize(int channelId = 0) => MaxAllowedPacketSize;
private bool ignoreDataPackets;
private string cachedConnectionAddress = string.Empty;
private IgnoranceServer Server = new IgnoranceServer();
private IgnoranceClient Client = new IgnoranceClient();
private Dictionary<int, PeerConnectionData> ConnectionLookupDict = new Dictionary<int, PeerConnectionData>();
private enum ConnectionState { Connecting, Connected, Disconnecting, Disconnected }
private ConnectionState ClientState = ConnectionState.Disconnected;
private byte[] InternalPacketBuffer;
public bool ServerDisconnectLegacy(int connectionId)
{
if (Server == null)
{
// Debug.LogError("Cannot enqueue kick packet; our Server object is null. Something has gone wrong.");
// Return here because otherwise we will get a NRE when trying to enqueue the kick packet.
return false;
}
IgnoranceCommandPacket kickPacket = new IgnoranceCommandPacket
{
Type = IgnoranceCommandType.ServerKickPeer,
PeerId = (uint)connectionId - 1 // ENet's native peer ID will be ConnID - 1
};
// Pass the packet onto the thread for dispatch.
Server.Commands.Enqueue(kickPacket);
return true;
}
}
}