Added KcpWebCombined, Disabled NAT/Direct on webgl, exposed node port

This commit is contained in:
Derek S 2021-04-11 15:49:13 -05:00
parent 011f562e63
commit d7d7b3fe9e
10 changed files with 342 additions and 11 deletions

View file

@ -93,6 +93,7 @@ To switch between them you have the following options:
* Mirror.TelepathyTransport
* kcp2k.KcpTransport
* Mirror.SimpleWebTransport
* MultiCompiled.KcpWebCombined
AuthenticationKey - This is the key the clients need to have on their inspector. It cannot be blank.

View file

@ -225,7 +225,9 @@ namespace Mirror
// ShoulderRotation.LateUpdate, resulting in projectile
// spawns at the point before shoulder rotation.
#pragma warning disable UNT0001 // Empty Unity message
public void Update() { }
public abstract void Update();
public abstract void Awake();
#pragma warning restore UNT0001 // Empty Unity message
/// <summary>

View file

@ -0,0 +1,56 @@
//
// Source: https://stackoverflow.com/questions/255341/getting-multiple-keys-of-specified-value-of-a-generic-dictionary#255630
//
using System;
using System.Collections.Generic;
namespace LightReflectiveMirror
{
class BiDictionary<TFirst, TSecond>
{
IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();
public void Add(TFirst first, TSecond second)
{
if (firstToSecond.ContainsKey(first) ||
secondToFirst.ContainsKey(second))
{
throw new ArgumentException("Duplicate first or second");
}
firstToSecond.Add(first, second);
secondToFirst.Add(second, first);
}
public bool TryGetByFirst(TFirst first, out TSecond second)
{
return firstToSecond.TryGetValue(first, out second);
}
public void Remove(TFirst first)
{
secondToFirst.Remove(firstToSecond[first]);
firstToSecond.Remove(first);
}
public ICollection<TFirst> GetAllKeys()
{
return secondToFirst.Values;
}
public bool TryGetBySecond(TSecond second, out TFirst first)
{
return secondToFirst.TryGetValue(second, out first);
}
public TSecond GetByFirst(TFirst first)
{
return firstToSecond[first];
}
public TFirst GetBySecond(TSecond second)
{
return secondToFirst[second];
}
}
}

View file

@ -39,7 +39,7 @@ namespace kcp2k
// log statistics for headless servers that can't show them in GUI
public bool statisticsLog;
void Awake()
public override void Awake()
{
KCPConfig conf = new KCPConfig();
@ -169,7 +169,7 @@ namespace kcp2k
public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId);
public override void ServerStop() => server.Stop();
public void Update()
public override void Update()
{
server.TickIncoming();
server.TickOutgoing();

View file

@ -0,0 +1,268 @@
using kcp2k;
using Mirror;
using System;
using System.Text;
namespace MultiCompiled
{
public class KcpWebCombined : Transport
{
public Transport[] transports;
Transport available;
public override void Awake()
{
transports = new Transport[2] { new KcpTransport(), new SimpleWebTransport() };
foreach (Transport transport in transports)
transport.Awake();
}
public override void Update()
{
foreach (Transport transport in transports)
{
transport.Update();
}
}
public override bool Available()
{
// available if any of the transports is available
foreach (Transport transport in transports)
{
if (transport.Available())
{
return true;
}
}
return false;
}
#region Client
public override void ClientConnect(string address)
{
foreach (Transport transport in transports)
{
if (transport.Available())
{
available = transport;
transport.OnClientConnected = OnClientConnected;
transport.OnClientDataReceived = OnClientDataReceived;
transport.OnClientError = OnClientError;
transport.OnClientDisconnected = OnClientDisconnected;
transport.ClientConnect(address);
return;
}
}
throw new ArgumentException("No transport suitable for this platform");
}
public override void ClientConnect(Uri uri)
{
foreach (Transport transport in transports)
{
if (transport.Available())
{
try
{
available = transport;
transport.OnClientConnected = OnClientConnected;
transport.OnClientDataReceived = OnClientDataReceived;
transport.OnClientError = OnClientError;
transport.OnClientDisconnected = OnClientDisconnected;
transport.ClientConnect(uri);
return;
}
catch (ArgumentException)
{
// transport does not support the schema, just move on to the next one
}
}
}
throw new ArgumentException("No transport suitable for this platform");
}
public override bool ClientConnected()
{
return (object)available != null && available.ClientConnected();
}
public override void ClientDisconnect()
{
if ((object)available != null)
available.ClientDisconnect();
}
public override void ClientSend(int channelId, ArraySegment<byte> segment)
{
available.ClientSend(channelId, segment);
}
#endregion
#region Server
// connection ids get mapped to base transports
// if we have 3 transports, then
// transport 0 will produce connection ids [0, 3, 6, 9, ...]
// transport 1 will produce connection ids [1, 4, 7, 10, ...]
// transport 2 will produce connection ids [2, 5, 8, 11, ...]
int FromBaseId(int transportId, int connectionId)
{
return connectionId * transports.Length + transportId;
}
int ToBaseId(int connectionId)
{
return connectionId / transports.Length;
}
int ToTransportId(int connectionId)
{
return connectionId % transports.Length;
}
void AddServerCallbacks()
{
// wire all the base transports to my events
for (int i = 0; i < transports.Length; i++)
{
// this is required for the handlers, if I use i directly
// then all the handlers will use the last i
int locali = i;
Transport transport = transports[i];
transport.OnServerConnected = (baseConnectionId =>
{
OnServerConnected.Invoke(FromBaseId(locali, baseConnectionId));
});
transport.OnServerDataReceived = (baseConnectionId, data, channel) =>
{
OnServerDataReceived.Invoke(FromBaseId(locali, baseConnectionId), data, channel);
};
transport.OnServerError = (baseConnectionId, error) =>
{
OnServerError.Invoke(FromBaseId(locali, baseConnectionId), error);
};
transport.OnServerDisconnected = baseConnectionId =>
{
OnServerDisconnected.Invoke(FromBaseId(locali, baseConnectionId));
};
}
}
// for now returns the first uri,
// should we return all available uris?
public override Uri ServerUri()
{
return transports[0].ServerUri();
}
public override bool ServerActive()
{
// avoid Linq.All allocations
foreach (Transport transport in transports)
{
if (!transport.ServerActive())
{
return false;
}
}
return true;
}
public override string ServerGetClientAddress(int connectionId)
{
int baseConnectionId = ToBaseId(connectionId);
int transportId = ToTransportId(connectionId);
return transports[transportId].ServerGetClientAddress(baseConnectionId);
}
public override bool ServerDisconnect(int connectionId)
{
int baseConnectionId = ToBaseId(connectionId);
int transportId = ToTransportId(connectionId);
return transports[transportId].ServerDisconnect(baseConnectionId);
}
public override void ServerSend(int connectionId, int channelId, ArraySegment<byte> segment)
{
int baseConnectionId = ToBaseId(connectionId);
int transportId = ToTransportId(connectionId);
for (int i = 0; i < transports.Length; ++i)
{
if (i == transportId)
{
transports[i].ServerSend(baseConnectionId, channelId, segment);
}
}
}
public override void ServerStart(ushort port)
{
foreach (Transport transport in transports)
{
AddServerCallbacks();
transport.ServerStart(port);
}
}
public override void ServerStop()
{
foreach (Transport transport in transports)
{
transport.ServerStop();
}
}
#endregion
public override int GetMaxPacketSize(int channelId = 0)
{
// finding the max packet size in a multiplex environment has to be
// done very carefully:
// * servers run multiple transports at the same time
// * different clients run different transports
// * there should only ever be ONE true max packet size for everyone,
// otherwise a spawn message might be sent to all tcp sockets, but
// be too big for some udp sockets. that would be a debugging
// nightmare and allow for possible exploits and players on
// different platforms seeing a different game state.
// => the safest solution is to use the smallest max size for all
// transports. that will never fail.
int mininumAllowedSize = int.MaxValue;
foreach (Transport transport in transports)
{
int size = transport.GetMaxPacketSize(channelId);
mininumAllowedSize = Math.Min(size, mininumAllowedSize);
}
return mininumAllowedSize;
}
public override void Shutdown()
{
foreach (Transport transport in transports)
{
transport.Shutdown();
}
}
public override string ToString()
{
StringBuilder builder = new StringBuilder();
foreach (Transport transport in transports)
{
builder.AppendLine(transport.ToString());
}
return builder.ToString().Trim();
}
}
}

View file

@ -78,7 +78,7 @@ namespace Mirror
return maxMessageSize;
}
void Awake()
public override void Awake()
{
Log.level = _logLevels;
@ -216,7 +216,7 @@ namespace Mirror
return builder.Uri;
}
public void Update()
public override void Update()
{
server?.ProcessMessageQueue();
}

View file

@ -44,7 +44,7 @@ namespace Mirror
// let's only allocate it once.
Func<bool> enabledCheck;
void Awake()
public override void Awake()
{
TelepathyConfig conf = new TelepathyConfig();
if (!File.Exists("TelepathyConfig.json"))
@ -162,7 +162,7 @@ namespace Mirror
}
public override void ServerStop() => server.Stop();
// messages should always be processed in early update
public void Update()
public override void Update()
{
// note: we need to check enabled in case we set it to false
// when LateUpdate already started.

View file

@ -230,6 +230,7 @@ namespace LightReflectiveMirror
GUI.enabled = false;
}
lrm.serverIP = EditorGUILayout.TextField("LRM Node IP", lrm.serverIP);
lrm.serverPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("LRM Node Port", lrm.serverPort), ushort.MinValue, ushort.MaxValue);
lrm.endpointServerPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("Endpoint Port", lrm.endpointServerPort), ushort.MinValue, ushort.MaxValue);
if (lrm.useLoadBalancer)

View file

@ -11,6 +11,7 @@ namespace LightReflectiveMirror
// Connection/auth variables
public Transport clientToServerTransport;
public string serverIP = null;
public ushort serverPort = 7777;
public ushort endpointServerPort = 8080;
public float heartBeatInterval = 3;
public bool connectOnAwake = true;

View file

@ -19,11 +19,14 @@ namespace LightReflectiveMirror
throw new Exception("LRM | Please switch to .NET 4.x for LRM to function properly!");
#endif
if (Application.platform == RuntimePlatform.WebGLPlayer)
useNATPunch = false;
else
_directConnectModule = GetComponent<LRMDirectConnectModule>();
if (clientToServerTransport is LightReflectiveMirrorTransport)
throw new Exception("Haha real funny... Use a different transport.");
_directConnectModule = GetComponent<LRMDirectConnectModule>();
if (_directConnectModule != null)
{
if (useNATPunch && !_directConnectModule.SupportsNATPunch())
@ -71,7 +74,7 @@ namespace LightReflectiveMirror
{
if (!_connectedToRelay)
{
Connect(serverIP);
Connect(serverIP, serverPort);
}
else
{
@ -103,7 +106,6 @@ namespace LightReflectiveMirror
SetTransportPort(port);
this.serverIP = serverIP;
serverStatus = "Connecting to relay...";
_clientSendBuffer = new byte[clientToServerTransport.GetMaxPacketSize()];