Merge pull request #619 from misternebula/update-mirror

Update Mirror
This commit is contained in:
_nebula 2023-04-26 23:49:28 +01:00 committed by GitHub
commit 9ab9158a50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1491 additions and 1608 deletions

View File

@ -50,12 +50,11 @@ using System.Collections.Generic;
/// MIT License
/// </summary>
namespace EpicTransport;
namespace EpicTransport {
public class BidirectionalDictionary<T1, T2> : IEnumerable
{
private Dictionary<T1, T2> t1ToT2Dict = new();
private Dictionary<T2, T1> t2ToT1Dict = new();
public class BidirectionalDictionary<T1, T2> : IEnumerable {
private Dictionary<T1, T2> t1ToT2Dict = new Dictionary<T1, T2>();
private Dictionary<T2, T1> t2ToT1Dict = new Dictionary<T2, T1>();
public IEnumerable<T1> FirstTypes => t1ToT2Dict.Keys;
public IEnumerable<T2> SecondTypes => t2ToT1Dict.Keys;
@ -64,14 +63,12 @@ public class BidirectionalDictionary<T1, T2> : IEnumerable
public int Count => t1ToT2Dict.Count;
public void Add(T1 key, T2 value)
{
public void Add(T1 key, T2 value) {
t1ToT2Dict[key] = value;
t2ToT1Dict[value] = key;
}
public void Add(T2 key, T1 value)
{
public void Add(T2 key, T1 value) {
t2ToT1Dict[key] = value;
t1ToT2Dict[value] = key;
}
@ -88,43 +85,36 @@ public class BidirectionalDictionary<T1, T2> : IEnumerable
public bool Contains(T2 key) => t2ToT1Dict.ContainsKey(key);
public void Remove(T1 key)
{
if (Contains(key))
{
var val = t1ToT2Dict[key];
public void Remove(T1 key) {
if (Contains(key)) {
T2 val = t1ToT2Dict[key];
t1ToT2Dict.Remove(key);
t2ToT1Dict.Remove(val);
}
}
public void Remove(T2 key)
{
if (Contains(key))
{
var val = t2ToT1Dict[key];
public void Remove(T2 key) {
if (Contains(key)) {
T1 val = t2ToT1Dict[key];
t1ToT2Dict.Remove(val);
t2ToT1Dict.Remove(key);
}
}
public T1 this[T2 key]
{
public T1 this[T2 key] {
get => t2ToT1Dict[key];
set
{
set {
t2ToT1Dict[key] = value;
t1ToT2Dict[value] = key;
}
}
public T2 this[T1 key]
{
public T2 this[T1 key] {
get => t1ToT2Dict[key];
set
{
set {
t1ToT2Dict[key] = value;
t2ToT1Dict[value] = key;
}
}
}
}

View File

@ -1,14 +1,14 @@
using Epic.OnlineServices;
using Epic.OnlineServices.P2P;
using Mirror;
using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace EpicTransport;
namespace EpicTransport {
public class Client : Common {
public class Client : Common
{
public SocketId socketId;
public ProductUserId serverId;
@ -18,6 +18,7 @@ public class Client : Common
private event Action<byte[], int> OnReceivedData;
private event Action OnConnected;
public event Action OnDisconnected;
// CHANGED
private Action<string> SetTransportError;
private TimeSpan ConnectionTimeout;
@ -28,11 +29,12 @@ public class Client : Common
private TaskCompletionSource<Task> connectedComplete;
private CancellationTokenSource cancelToken;
private Client(EosTransport transport) : base(transport) => ConnectionTimeout = TimeSpan.FromSeconds(Math.Max(1, transport.timeout));
private Client(EosTransport transport) : base(transport) {
ConnectionTimeout = TimeSpan.FromSeconds(Math.Max(1, transport.timeout));
}
public static Client CreateClient(EosTransport transport, string host)
{
var c = new Client(transport);
public static Client CreateClient(EosTransport transport, string host) {
Client c = new Client(transport);
c.hostAddress = host;
c.socketId = new SocketId() { SocketName = RandomString.Generate(20) };
@ -40,17 +42,16 @@ public class Client : Common
c.OnConnected += () => transport.OnClientConnected.Invoke();
c.OnDisconnected += () => transport.OnClientDisconnected.Invoke();
c.OnReceivedData += (data, channel) => transport.OnClientDataReceived.Invoke(new ArraySegment<byte>(data), channel);
// CHANGED
c.SetTransportError = transport.SetTransportError;
return c;
}
public async void Connect(string host)
{
public async void Connect(string host) {
cancelToken = new CancellationTokenSource();
try
{
try {
hostProductId = ProductUserId.FromString(host);
serverId = hostProductId;
connectedComplete = new TaskCompletionSource<Task>();
@ -61,8 +62,8 @@ public class Client : Common
Task connectedCompleteTask = connectedComplete.Task;
if (await Task.WhenAny(connectedCompleteTask, Task.Delay(ConnectionTimeout /*, cancelToken.Token*/)) != connectedCompleteTask)
{
if (await Task.WhenAny(connectedCompleteTask, Task.Delay(ConnectionTimeout/*, cancelToken.Token*/)) != connectedCompleteTask) {
// CHANGED
SetTransportError($"Connection to {host} timed out.");
Debug.LogError($"Connection to {host} timed out.");
OnConnected -= SetConnectedComplete;
@ -70,40 +71,32 @@ public class Client : Common
}
OnConnected -= SetConnectedComplete;
}
catch (FormatException)
{
} catch (FormatException) {
// CHANGED
SetTransportError("Connection string was not in the right format. Did you enter a ProductId?");
Debug.LogError($"Connection string was not in the right format. Did you enter a ProductId?");
Error = true;
OnConnectionFailed(hostProductId);
}
catch (Exception ex)
{
} catch (Exception ex) {
// CHANGED
SetTransportError(ex.Message);
Debug.LogError(ex.Message);
Error = true;
OnConnectionFailed(hostProductId);
}
finally
{
if (Error)
{
} finally {
if (Error) {
OnConnectionFailed(null);
}
}
}
public void Disconnect()
{
if (serverId != null)
{
public void Disconnect() {
if (serverId != null) {
CloseP2PSessionWithUser(serverId, socketId);
serverId = null;
}
else
{
} else {
return;
}
@ -117,15 +110,12 @@ public class Client : Common
private void SetConnectedComplete() => connectedComplete.SetResult(connectedComplete.Task);
protected override void OnReceiveData(byte[] data, ProductUserId clientUserId, int channel)
{
if (ignoreAllMessages)
{
protected override void OnReceiveData(byte[] data, ProductUserId clientUserId, int channel) {
if (ignoreAllMessages) {
return;
}
if (clientUserId != hostProductId)
{
if (clientUserId != hostProductId) {
Debug.LogError("Received a message from an unknown");
return;
}
@ -133,50 +123,41 @@ public class Client : Common
OnReceivedData.Invoke(data, channel);
}
protected override void OnNewConnection(OnIncomingConnectionRequestInfo result)
{
if (ignoreAllMessages)
{
protected override void OnNewConnection(OnIncomingConnectionRequestInfo result) {
if (ignoreAllMessages) {
return;
}
if (deadSockets.Contains(result.SocketId.SocketName))
{
if (deadSockets.Contains(result.SocketId.SocketName)) {
Debug.LogError("Received incoming connection request from dead socket");
return;
}
if (hostProductId == result.RemoteUserId)
{
if (hostProductId == result.RemoteUserId) {
EOSSDKComponent.GetP2PInterface().AcceptConnection(
new AcceptConnectionOptions()
{
new AcceptConnectionOptions() {
LocalUserId = EOSSDKComponent.LocalUserProductId,
RemoteUserId = result.RemoteUserId,
SocketId = result.SocketId
});
}
else
{
} else {
Debug.LogError("P2P Acceptance Request from unknown host ID.");
}
}
protected override void OnReceiveInternalData(InternalMessages type, ProductUserId clientUserId, SocketId socketId)
{
if (ignoreAllMessages)
{
protected override void OnReceiveInternalData(InternalMessages type, ProductUserId clientUserId, SocketId socketId) {
if (ignoreAllMessages) {
return;
}
switch (type)
{
switch (type) {
case InternalMessages.ACCEPT_CONNECT:
Connected = true;
OnConnected.Invoke();
Debug.Log("Connection established.");
break;
case InternalMessages.DISCONNECT:
// CHANGED
SetTransportError("host disconnected");
Connected = false;
Debug.Log("Disconnected.");
@ -189,8 +170,9 @@ public class Client : Common
}
}
public void Send(byte[] data, int channelId) => Send(hostProductId, socketId, data, (byte)channelId);
public void Send(byte[] data, int channelId) => Send(hostProductId, socketId, data, (byte) channelId);
protected override void OnConnectionFailed(ProductUserId remoteId) => OnDisconnected.Invoke();
public void EosNotInitialized() => OnDisconnected.Invoke();
}
}

View File

@ -1,34 +1,32 @@
using Epic.OnlineServices;

using Epic.OnlineServices;
using Epic.OnlineServices.P2P;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace EpicTransport;
namespace EpicTransport {
public abstract class Common {
public abstract class Common
{
private PacketReliability[] channels;
private int internal_ch => channels.Length;
protected enum InternalMessages : byte
{
protected enum InternalMessages : byte {
CONNECT,
ACCEPT_CONNECT,
DISCONNECT
}
protected struct PacketKey
{
protected struct PacketKey {
public ProductUserId productUserId;
public byte channel;
}
private OnIncomingConnectionRequestCallback OnIncomingConnectionRequest;
private ulong incomingNotificationId = 0;
ulong incomingNotificationId = 0;
private OnRemoteConnectionClosedCallback OnRemoteConnectionClosed;
private ulong outgoingNotificationId = 0;
ulong outgoingNotificationId = 0;
protected readonly EosTransport transport;
@ -36,15 +34,14 @@ public abstract class Common
public bool ignoreAllMessages = false;
// Mapping from PacketKey to a List of Packet Lists
protected Dictionary<PacketKey, List<List<Packet>>> incomingPackets = new();
protected Dictionary<PacketKey, List<List<Packet>>> incomingPackets = new Dictionary<PacketKey, List<List<Packet>>>();
protected Common(EosTransport transport)
{
protected Common(EosTransport transport) {
channels = transport.Channels;
deadSockets = new List<string>();
var addNotifyPeerConnectionRequestOptions = new AddNotifyPeerConnectionRequestOptions();
AddNotifyPeerConnectionRequestOptions addNotifyPeerConnectionRequestOptions = new AddNotifyPeerConnectionRequestOptions();
addNotifyPeerConnectionRequestOptions.LocalUserId = EOSSDKComponent.LocalUserProductId;
addNotifyPeerConnectionRequestOptions.SocketId = null;
@ -54,25 +51,24 @@ public abstract class Common
incomingNotificationId = EOSSDKComponent.GetP2PInterface().AddNotifyPeerConnectionRequest(addNotifyPeerConnectionRequestOptions,
null, OnIncomingConnectionRequest);
var addNotifyPeerConnectionClosedOptions = new AddNotifyPeerConnectionClosedOptions();
AddNotifyPeerConnectionClosedOptions addNotifyPeerConnectionClosedOptions = new AddNotifyPeerConnectionClosedOptions();
addNotifyPeerConnectionClosedOptions.LocalUserId = EOSSDKComponent.LocalUserProductId;
addNotifyPeerConnectionClosedOptions.SocketId = null;
outgoingNotificationId = EOSSDKComponent.GetP2PInterface().AddNotifyPeerConnectionClosed(addNotifyPeerConnectionClosedOptions,
null, OnRemoteConnectionClosed);
if (outgoingNotificationId == 0 || incomingNotificationId == 0)
{
if (outgoingNotificationId == 0 || incomingNotificationId == 0) {
Debug.LogError("Couldn't bind notifications with P2P interface");
}
incomingPackets = new Dictionary<PacketKey, List<List<Packet>>>();
this.transport = transport;
}
protected void Dispose()
{
protected void Dispose() {
EOSSDKComponent.GetP2PInterface().RemoveNotifyPeerConnectionRequest(incomingNotificationId);
EOSSDKComponent.GetP2PInterface().RemoveNotifyPeerConnectionClosed(outgoingNotificationId);
@ -81,17 +77,14 @@ public abstract class Common
protected abstract void OnNewConnection(OnIncomingConnectionRequestInfo result);
private void OnConnectFail(OnRemoteConnectionClosedInfo result)
{
if (ignoreAllMessages)
{
private void OnConnectFail(OnRemoteConnectionClosedInfo result) {
if (ignoreAllMessages) {
return;
}
OnConnectionFailed(result.RemoteUserId);
switch (result.Reason)
{
switch (result.Reason) {
case ConnectionClosedReason.ClosedByLocalUser:
throw new Exception("Connection cLosed: The Connection was gracecfully closed by the local user.");
case ConnectionClosedReason.ClosedByPeer:
@ -118,13 +111,11 @@ public abstract class Common
}
}
protected void SendInternal(ProductUserId target, SocketId socketId, InternalMessages type)
{
EOSSDKComponent.GetP2PInterface().SendPacket(new SendPacketOptions()
{
protected void SendInternal(ProductUserId target, SocketId socketId, InternalMessages type) {
EOSSDKComponent.GetP2PInterface().SendPacket(new SendPacketOptions() {
AllowDelayedDelivery = true,
Channel = (byte)internal_ch,
Data = new byte[] { (byte)type },
Channel = (byte) internal_ch,
Data = new byte[] { (byte) type },
LocalUserId = EOSSDKComponent.LocalUserProductId,
Reliability = PacketReliability.ReliableOrdered,
RemoteUserId = target,
@ -132,10 +123,9 @@ public abstract class Common
});
}
protected void Send(ProductUserId host, SocketId socketId, byte[] msgBuffer, byte channel)
{
var result = EOSSDKComponent.GetP2PInterface().SendPacket(new SendPacketOptions()
{
protected void Send(ProductUserId host, SocketId socketId, byte[] msgBuffer, byte channel) {
Result result = EOSSDKComponent.GetP2PInterface().SendPacket(new SendPacketOptions() {
AllowDelayedDelivery = true,
Channel = channel,
Data = msgBuffer,
@ -145,23 +135,19 @@ public abstract class Common
SocketId = socketId
});
if (result != Result.Success)
{
if(result != Result.Success) {
Debug.LogError("Send failed " + result);
}
}
private bool Receive(out ProductUserId clientProductUserId, out SocketId socketId, out byte[] receiveBuffer, byte channel)
{
var result = EOSSDKComponent.GetP2PInterface().ReceivePacket(new ReceivePacketOptions()
{
private bool Receive(out ProductUserId clientProductUserId, out SocketId socketId, out byte[] receiveBuffer, byte channel) {
Result result = EOSSDKComponent.GetP2PInterface().ReceivePacket(new ReceivePacketOptions() {
LocalUserId = EOSSDKComponent.LocalUserProductId,
MaxDataSizeBytes = P2PInterface.MaxPacketSize,
RequestedChannel = channel
}, out clientProductUserId, out socketId, out channel, out receiveBuffer);
if (result == Result.Success)
{
if (result == Result.Success) {
return true;
}
@ -170,160 +156,127 @@ public abstract class Common
return false;
}
protected virtual void CloseP2PSessionWithUser(ProductUserId clientUserID, SocketId socketId)
{
if (socketId == null)
{
protected virtual void CloseP2PSessionWithUser(ProductUserId clientUserID, SocketId socketId) {
if (socketId == null) {
Debug.LogWarning("Socket ID == null | " + ignoreAllMessages);
return;
}
if (deadSockets == null)
{
if (deadSockets == null) {
Debug.LogWarning("DeadSockets == null");
return;
}
if (deadSockets.Contains(socketId.SocketName))
{
if (deadSockets.Contains(socketId.SocketName)) {
return;
}
else
{
} else {
deadSockets.Add(socketId.SocketName);
}
}
protected void WaitForClose(ProductUserId clientUserID, SocketId socketId) => transport.StartCoroutine(DelayedClose(clientUserID, socketId));
private IEnumerator DelayedClose(ProductUserId clientUserID, SocketId socketId)
{
protected void WaitForClose(ProductUserId clientUserID, SocketId socketId) => transport.StartCoroutine(DelayedClose(clientUserID, socketId));
private IEnumerator DelayedClose(ProductUserId clientUserID, SocketId socketId) {
yield return null;
CloseP2PSessionWithUser(clientUserID, socketId);
}
public void ReceiveData()
{
try
{
public void ReceiveData() {
try {
// Internal Channel, no fragmentation here
var socketId = new SocketId();
while (transport.enabled && Receive(out var clientUserID, out socketId, out var internalMessage, (byte)internal_ch))
{
if (internalMessage.Length == 1)
{
OnReceiveInternalData((InternalMessages)internalMessage[0], clientUserID, socketId);
SocketId socketId = new SocketId();
while (transport.enabled && Receive(out ProductUserId clientUserID, out socketId, out byte[] internalMessage, (byte) internal_ch)) {
if (internalMessage.Length == 1) {
OnReceiveInternalData((InternalMessages) internalMessage[0], clientUserID, socketId);
return; // Wait one frame
}
else
{
} else {
Debug.Log("Incorrect package length on internal channel.");
}
}
// Insert new packet at the correct location in the incoming queue
for (var chNum = 0; chNum < channels.Length; chNum++)
{
while (transport.enabled && Receive(out var clientUserID, out socketId, out var receiveBuffer, (byte)chNum))
{
var incomingPacketKey = new PacketKey();
for (int chNum = 0; chNum < channels.Length; chNum++) {
while (transport.enabled && Receive(out ProductUserId clientUserID, out socketId, out byte[] receiveBuffer, (byte) chNum)) {
PacketKey incomingPacketKey = new PacketKey();
incomingPacketKey.productUserId = clientUserID;
incomingPacketKey.channel = (byte)chNum;
var packet = new Packet();
Packet packet = new Packet();
packet.FromBytes(receiveBuffer);
if (!incomingPackets.ContainsKey(incomingPacketKey))
{
if (!incomingPackets.ContainsKey(incomingPacketKey)) {
incomingPackets.Add(incomingPacketKey, new List<List<Packet>>());
}
var packetListIndex = incomingPackets[incomingPacketKey].Count;
for (var i = 0; i < incomingPackets[incomingPacketKey].Count; i++)
{
if (incomingPackets[incomingPacketKey][i][0].id == packet.id)
{
int packetListIndex = incomingPackets[incomingPacketKey].Count;
for(int i = 0; i < incomingPackets[incomingPacketKey].Count; i++) {
if(incomingPackets[incomingPacketKey][i][0].id == packet.id) {
packetListIndex = i;
break;
}
}
if (packetListIndex == incomingPackets[incomingPacketKey].Count)
{
if (packetListIndex == incomingPackets[incomingPacketKey].Count) {
incomingPackets[incomingPacketKey].Add(new List<Packet>());
}
var insertionIndex = -1;
int insertionIndex = -1;
for (var i = 0; i < incomingPackets[incomingPacketKey][packetListIndex].Count; i++)
{
if (incomingPackets[incomingPacketKey][packetListIndex][i].fragment > packet.fragment)
{
for (int i = 0; i < incomingPackets[incomingPacketKey][packetListIndex].Count; i++) {
if (incomingPackets[incomingPacketKey][packetListIndex][i].fragment > packet.fragment) {
insertionIndex = i;
break;
}
}
if (insertionIndex >= 0)
{
if (insertionIndex >= 0) {
incomingPackets[incomingPacketKey][packetListIndex].Insert(insertionIndex, packet);
}
else
{
} else {
incomingPackets[incomingPacketKey][packetListIndex].Add(packet);
}
}
}
// Find fully received packets
var emptyPacketLists = new List<List<Packet>>();
foreach (var keyValuePair in incomingPackets)
{
for (var packetList = 0; packetList < keyValuePair.Value.Count; packetList++)
{
var packetReady = true;
var packetLength = 0;
for (var packet = 0; packet < keyValuePair.Value[packetList].Count; packet++)
{
var tempPacket = keyValuePair.Value[packetList][packet];
if (tempPacket.fragment != packet || packet == keyValuePair.Value[packetList].Count - 1 && tempPacket.moreFragments)
{
List<List<Packet>> emptyPacketLists = new List<List<Packet>>();
foreach(KeyValuePair<PacketKey, List<List<Packet>>> keyValuePair in incomingPackets) {
for(int packetList = 0; packetList < keyValuePair.Value.Count; packetList++) {
bool packetReady = true;
int packetLength = 0;
for (int packet = 0; packet < keyValuePair.Value[packetList].Count; packet++) {
Packet tempPacket = keyValuePair.Value[packetList][packet];
if (tempPacket.fragment != packet || (packet == keyValuePair.Value[packetList].Count - 1 && tempPacket.moreFragments)) {
packetReady = false;
}
else
{
} else {
packetLength += tempPacket.data.Length;
}
}
if (packetReady)
{
var data = new byte[packetLength];
var dataIndex = 0;
if (packetReady) {
byte[] data = new byte[packetLength];
int dataIndex = 0;
for (var packet = 0; packet < keyValuePair.Value[packetList].Count; packet++)
{
for (int packet = 0; packet < keyValuePair.Value[packetList].Count; packet++) {
Array.Copy(keyValuePair.Value[packetList][packet].data, 0, data, dataIndex, keyValuePair.Value[packetList][packet].data.Length);
dataIndex += keyValuePair.Value[packetList][packet].data.Length;
}
OnReceiveData(data, keyValuePair.Key.productUserId, keyValuePair.Key.channel);
//keyValuePair.Value[packetList].Clear();
if(transport.ServerActive() || transport.ClientActive())
emptyPacketLists.Add(keyValuePair.Value[packetList]);
}
}
for (var i = 0; i < emptyPacketLists.Count; i++)
{
for (int i = 0; i < emptyPacketLists.Count; i++) {
keyValuePair.Value.Remove(emptyPacketLists[i]);
}
emptyPacketLists.Clear();
}
}
catch (Exception e)
{
} catch (Exception e) {
Debug.LogException(e);
}
}
@ -331,4 +284,5 @@ public abstract class Common
protected abstract void OnReceiveInternalData(InternalMessages type, ProductUserId clientUserID, SocketId socketId);
protected abstract void OnReceiveData(byte[] data, ProductUserId clientUserID, int channel);
protected abstract void OnConnectionFailed(ProductUserId remoteId);
}
}

View File

@ -1,6 +1,10 @@
using Epic.OnlineServices;
using Epic.OnlineServices.Logging;
using Epic.OnlineServices.Platform;
using System;
using System.Runtime.InteropServices;
using UnityEngine;
/// <summary>
@ -10,39 +14,42 @@ using UnityEngine;
/// after releasing the SDK the game has to be restarted in order to initialize the SDK again.
/// In the unity editor the OnDestroy function will not run so that we dont have to restart the editor after play.
/// </summary>
namespace EpicTransport;
namespace EpicTransport {
[DefaultExecutionOrder(-32000)]
public class EOSSDKComponent : MonoBehaviour {
[DefaultExecutionOrder(-32000)]
public class EOSSDKComponent : MonoBehaviour
{
// Unity Inspector shown variables
[SerializeField]
// CHANGED
public EosApiKey apiKeys;
[Header("User Login")]
public bool authInterfaceLogin = false;
public Epic.OnlineServices.Auth.LoginCredentialType authInterfaceCredentialType = Epic.OnlineServices.Auth.LoginCredentialType.AccountPortal;
public uint devAuthToolPort = 7878;
public string devAuthToolCredentialName = "";
public ExternalCredentialType connectInterfaceCredentialType = ExternalCredentialType.DeviceidAccessToken;
public Epic.OnlineServices.ExternalCredentialType connectInterfaceCredentialType = Epic.OnlineServices.ExternalCredentialType.DeviceidAccessToken;
public string deviceModel = "PC Windows 64bit";
[SerializeField] private string displayName = "User";
public static string DisplayName
{
get => Instance.displayName;
set => Instance.displayName = value;
public static string DisplayName {
get {
return Instance.displayName;
}
set {
Instance.displayName = value;
}
}
[Header("Misc")]
public LogLevel epicLoggerLevel = LogLevel.Error;
[SerializeField]
private bool collectPlayerMetrics = true;
public static bool CollectPlayerMetrics => Instance.collectPlayerMetrics;
[SerializeField] private bool collectPlayerMetrics = true;
public static bool CollectPlayerMetrics {
get {
return Instance.collectPlayerMetrics;
}
}
public bool checkForEpicLauncherAndRestart = false;
public bool delayedInitialization = false;
@ -54,6 +61,7 @@ public class EOSSDKComponent : MonoBehaviour
private ulong authExpirationHandle;
private string authInterfaceLoginCredentialId = null;
public static void SetAuthInterfaceLoginCredentialId(string credentialId) => Instance.authInterfaceLoginCredentialId = credentialId;
private string authInterfaceCredentialToken = null;
@ -81,43 +89,61 @@ public class EOSSDKComponent : MonoBehaviour
public static Epic.OnlineServices.UI.UIInterface GetUIInterface() => Instance.EOS.GetUIInterface();
public static Epic.OnlineServices.UserInfo.UserInfoInterface GetUserInfoInterface() => Instance.EOS.GetUserInfoInterface();
protected EpicAccountId localUserAccountId;
public static EpicAccountId LocalUserAccountId => Instance.localUserAccountId;
public static EpicAccountId LocalUserAccountId {
get {
return Instance.localUserAccountId;
}
}
protected string localUserAccountIdString;
public static string LocalUserAccountIdString => Instance.localUserAccountIdString;
public static string LocalUserAccountIdString {
get {
return Instance.localUserAccountIdString;
}
}
protected ProductUserId localUserProductId;
public static ProductUserId LocalUserProductId => Instance.localUserProductId;
public static ProductUserId LocalUserProductId {
get {
return Instance.localUserProductId;
}
}
protected string localUserProductIdString;
public static string LocalUserProductIdString => Instance.localUserProductIdString;
public static string LocalUserProductIdString {
get {
return Instance.localUserProductIdString;
}
}
protected bool initialized;
public static bool Initialized => Instance.initialized;
public static bool Initialized {
get {
return Instance.initialized;
}
}
protected bool isConnecting;
public static bool IsConnecting => Instance.isConnecting;
public static bool IsConnecting {
get {
return Instance.isConnecting;
}
}
protected static EOSSDKComponent instance;
protected static EOSSDKComponent Instance
{
get
{
if (instance == null)
{
protected static EOSSDKComponent Instance {
get {
if (instance == null) {
return new GameObject("EOSSDKComponent").AddComponent<EOSSDKComponent>();
}
else
{
} else {
return instance;
}
}
}
public static void Tick()
{
public static void Tick() {
instance.platformTickTimer -= Time.deltaTime;
instance.EOS.Tick();
}
@ -163,26 +189,23 @@ public class EOSSDKComponent : MonoBehaviour
private IntPtr libraryPointer;
#endif
private void Awake()
{
private void Awake() {
// Initialize Java version of the SDK with a reference to the VM with JNI
// See https://eoshelp.epicgames.com/s/question/0D54z00006ufJBNCA2/cant-get-createdeviceid-to-work-in-unity-android-c-sdk?language=en_US
if (Application.platform == RuntimePlatform.Android)
{
var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
var context = activity.Call<AndroidJavaObject>("getApplicationContext");
var EOS_SDK_JAVA = new AndroidJavaClass("com.epicgames.mobile.eossdk.EOSSDK");
AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
AndroidJavaObject context = activity.Call<AndroidJavaObject>("getApplicationContext");
AndroidJavaClass EOS_SDK_JAVA = new AndroidJavaClass("com.epicgames.mobile.eossdk.EOSSDK");
EOS_SDK_JAVA.CallStatic("init", context);
}
// Prevent multiple instances
if (instance != null)
{
if (instance != null) {
Destroy(gameObject);
return;
}
instance = this;
#if UNITY_EDITOR
@ -196,18 +219,15 @@ public class EOSSDKComponent : MonoBehaviour
Bindings.Hook(libraryPointer, GetProcAddress);
#endif
if (!delayedInitialization)
{
if (!delayedInitialization) {
Initialize();
}
}
protected void InitializeImplementation()
{
protected void InitializeImplementation() {
isConnecting = true;
var initializeOptions = new InitializeOptions()
{
var initializeOptions = new InitializeOptions() {
ProductName = apiKeys.epicProductName,
ProductVersion = apiKeys.epicProductVersion
};
@ -216,8 +236,7 @@ public class EOSSDKComponent : MonoBehaviour
// This code is called each time the game is run in the editor, so we catch the case where the SDK has already been initialized in the editor.
var isAlreadyConfiguredInEditor = Application.isEditor && initializeResult == Result.AlreadyConfigured;
if (initializeResult != Result.Success && !isAlreadyConfiguredInEditor)
{
if (initializeResult != Result.Success && !isAlreadyConfiguredInEditor) {
throw new System.Exception("Failed to initialize platform: " + initializeResult);
}
@ -226,13 +245,11 @@ public class EOSSDKComponent : MonoBehaviour
LoggingInterface.SetLogLevel(LogCategory.AllCategories, epicLoggerLevel);
LoggingInterface.SetCallback(message => Logger.EpicDebugLog(message));
var options = new Options()
{
var options = new Options() {
ProductId = apiKeys.epicProductId,
SandboxId = apiKeys.epicSandboxId,
DeploymentId = apiKeys.epicDeploymentId,
ClientCredentials = new ClientCredentials()
{
ClientCredentials = new ClientCredentials() {
ClientId = apiKeys.epicClientId,
ClientSecret = apiKeys.epicClientSecret
},
@ -240,21 +257,18 @@ public class EOSSDKComponent : MonoBehaviour
};
EOS = PlatformInterface.Create(options);
if (EOS == null)
{
if (EOS == null) {
throw new System.Exception("Failed to create platform");
}
if (checkForEpicLauncherAndRestart)
{
var result = EOS.CheckForLauncherAndRestart();
if (checkForEpicLauncherAndRestart) {
Result result = EOS.CheckForLauncherAndRestart();
// If not started through epic launcher the app will be restarted and we can quit
if (result != Result.NoChange)
{
if (result != Result.NoChange) {
// Log error if launcher check failed, but still quit to prevent hacking
if (result == Result.UnexpectedError)
{
if (result == Result.UnexpectedError) {
Debug.LogError("Unexpected Error while checking if app was started through epic launcher");
}
@ -264,19 +278,15 @@ public class EOSSDKComponent : MonoBehaviour
// If we use the Auth interface then only login into the Connect interface after finishing the auth interface login
// If we don't use the Auth interface we can directly login to the Connect interface
if (authInterfaceLogin)
{
if (authInterfaceCredentialType == Epic.OnlineServices.Auth.LoginCredentialType.Developer)
{
if (authInterfaceLogin) {
if (authInterfaceCredentialType == Epic.OnlineServices.Auth.LoginCredentialType.Developer) {
authInterfaceLoginCredentialId = "localhost:" + devAuthToolPort;
authInterfaceCredentialToken = devAuthToolCredentialName;
}
// Login to Auth Interface
var loginOptions = new Epic.OnlineServices.Auth.LoginOptions()
{
Credentials = new Epic.OnlineServices.Auth.Credentials()
{
Epic.OnlineServices.Auth.LoginOptions loginOptions = new Epic.OnlineServices.Auth.LoginOptions() {
Credentials = new Epic.OnlineServices.Auth.Credentials() {
Type = authInterfaceCredentialType,
Id = authInterfaceLoginCredentialId,
Token = authInterfaceCredentialToken
@ -285,43 +295,33 @@ public class EOSSDKComponent : MonoBehaviour
};
EOS.GetAuthInterface().Login(loginOptions, null, OnAuthInterfaceLogin);
}
else
{
} else {
// Login to Connect Interface
if (connectInterfaceCredentialType == ExternalCredentialType.DeviceidAccessToken)
{
var createDeviceIdOptions = new Epic.OnlineServices.Connect.CreateDeviceIdOptions();
if (connectInterfaceCredentialType == Epic.OnlineServices.ExternalCredentialType.DeviceidAccessToken) {
Epic.OnlineServices.Connect.CreateDeviceIdOptions createDeviceIdOptions = new Epic.OnlineServices.Connect.CreateDeviceIdOptions();
createDeviceIdOptions.DeviceModel = deviceModel;
EOS.GetConnectInterface().CreateDeviceId(createDeviceIdOptions, null, OnCreateDeviceId);
}
else
{
} else {
ConnectInterfaceLogin();
}
}
}
public static void Initialize()
{
if (Instance.initialized || Instance.isConnecting)
{
}
public static void Initialize() {
if (Instance.initialized || Instance.isConnecting) {
return;
}
Instance.InitializeImplementation();
}
private void OnAuthInterfaceLogin(Epic.OnlineServices.Auth.LoginCallbackInfo loginCallbackInfo)
{
if (loginCallbackInfo.ResultCode == Result.Success)
{
private void OnAuthInterfaceLogin(Epic.OnlineServices.Auth.LoginCallbackInfo loginCallbackInfo) {
if (loginCallbackInfo.ResultCode == Result.Success) {
Debug.Log("Auth Interface Login succeeded");
string accountIdString;
var result = loginCallbackInfo.LocalUserId.ToString(out accountIdString);
if (Result.Success == result)
{
Result result = loginCallbackInfo.LocalUserId.ToString(out accountIdString);
if (Result.Success == result) {
Debug.Log("EOS User ID:" + accountIdString);
localUserAccountIdString = accountIdString;
@ -329,45 +329,32 @@ public class EOSSDKComponent : MonoBehaviour
}
ConnectInterfaceLogin();
}
else if (Epic.OnlineServices.Common.IsOperationComplete(loginCallbackInfo.ResultCode))
{
} else if(Epic.OnlineServices.Common.IsOperationComplete(loginCallbackInfo.ResultCode)){
Debug.Log("Login returned " + loginCallbackInfo.ResultCode);
}
}
private void OnCreateDeviceId(Epic.OnlineServices.Connect.CreateDeviceIdCallbackInfo createDeviceIdCallbackInfo)
{
if (createDeviceIdCallbackInfo.ResultCode == Result.Success || createDeviceIdCallbackInfo.ResultCode == Result.DuplicateNotAllowed)
{
private void OnCreateDeviceId(Epic.OnlineServices.Connect.CreateDeviceIdCallbackInfo createDeviceIdCallbackInfo) {
if (createDeviceIdCallbackInfo.ResultCode == Result.Success || createDeviceIdCallbackInfo.ResultCode == Result.DuplicateNotAllowed) {
ConnectInterfaceLogin();
}
else if (Epic.OnlineServices.Common.IsOperationComplete(createDeviceIdCallbackInfo.ResultCode))
{
} else if(Epic.OnlineServices.Common.IsOperationComplete(createDeviceIdCallbackInfo.ResultCode)) {
Debug.Log("Device ID creation returned " + createDeviceIdCallbackInfo.ResultCode);
}
}
private void ConnectInterfaceLogin()
{
private void ConnectInterfaceLogin() {
var loginOptions = new Epic.OnlineServices.Connect.LoginOptions();
if (connectInterfaceCredentialType == ExternalCredentialType.Epic)
{
if (connectInterfaceCredentialType == Epic.OnlineServices.ExternalCredentialType.Epic) {
Epic.OnlineServices.Auth.Token token;
var result = EOS.GetAuthInterface().CopyUserAuthToken(new Epic.OnlineServices.Auth.CopyUserAuthTokenOptions(), localUserAccountId, out token);
Result result = EOS.GetAuthInterface().CopyUserAuthToken(new Epic.OnlineServices.Auth.CopyUserAuthTokenOptions(), localUserAccountId, out token);
if (result == Result.Success)
{
if (result == Result.Success) {
connectInterfaceCredentialToken = token.AccessToken;
}
else
{
} else {
Debug.LogError("Failed to retrieve User Auth Token");
}
}
else if (connectInterfaceCredentialType == ExternalCredentialType.DeviceidAccessToken)
{
} else if (connectInterfaceCredentialType == Epic.OnlineServices.ExternalCredentialType.DeviceidAccessToken) {
loginOptions.UserLoginInfo = new Epic.OnlineServices.Connect.UserLoginInfo();
loginOptions.UserLoginInfo.DisplayName = displayName;
}
@ -379,16 +366,13 @@ public class EOSSDKComponent : MonoBehaviour
EOS.GetConnectInterface().Login(loginOptions, null, OnConnectInterfaceLogin);
}
private void OnConnectInterfaceLogin(Epic.OnlineServices.Connect.LoginCallbackInfo loginCallbackInfo)
{
if (loginCallbackInfo.ResultCode == Result.Success)
{
private void OnConnectInterfaceLogin(Epic.OnlineServices.Connect.LoginCallbackInfo loginCallbackInfo) {
if (loginCallbackInfo.ResultCode == Result.Success) {
Debug.Log("Connect Interface Login succeeded");
string productIdString;
var result = loginCallbackInfo.LocalUserId.ToString(out productIdString);
if (Result.Success == result)
{
Result result = loginCallbackInfo.LocalUserId.ToString(out productIdString);
if (Result.Success == result) {
Debug.Log("EOS User Product ID:" + productIdString);
localUserProductIdString = productIdString;
@ -400,50 +384,36 @@ public class EOSSDKComponent : MonoBehaviour
var authExpirationOptions = new Epic.OnlineServices.Connect.AddNotifyAuthExpirationOptions();
authExpirationHandle = EOS.GetConnectInterface().AddNotifyAuthExpiration(authExpirationOptions, null, OnAuthExpiration);
}
else if (Epic.OnlineServices.Common.IsOperationComplete(loginCallbackInfo.ResultCode))
{
} else if (Epic.OnlineServices.Common.IsOperationComplete(loginCallbackInfo.ResultCode)) {
Debug.Log("Login returned " + loginCallbackInfo.ResultCode + "\nRetrying...");
EOS.GetConnectInterface().CreateUser(new Epic.OnlineServices.Connect.CreateUserOptions() { ContinuanceToken = loginCallbackInfo.ContinuanceToken }, null, (Epic.OnlineServices.Connect.CreateUserCallbackInfo cb) =>
{
if (cb.ResultCode != Result.Success)
{
Debug.Log(cb.ResultCode);
return;
}
EOS.GetConnectInterface().CreateUser(new Epic.OnlineServices.Connect.CreateUserOptions() { ContinuanceToken = loginCallbackInfo.ContinuanceToken }, null, (Epic.OnlineServices.Connect.CreateUserCallbackInfo cb) => {
if (cb.ResultCode != Result.Success) { Debug.Log(cb.ResultCode); return; }
localUserProductId = cb.LocalUserId;
ConnectInterfaceLogin();
});
}
}
private void OnAuthExpiration(Epic.OnlineServices.Connect.AuthExpirationCallbackInfo authExpirationCallbackInfo)
{
private void OnAuthExpiration(Epic.OnlineServices.Connect.AuthExpirationCallbackInfo authExpirationCallbackInfo) {
Debug.Log("AuthExpiration callback");
EOS.GetConnectInterface().RemoveNotifyAuthExpiration(authExpirationHandle);
ConnectInterfaceLogin();
}
// Calling tick on a regular interval is required for callbacks to work.
private void LateUpdate()
{
if (EOS != null)
{
private void LateUpdate() {
if (EOS != null) {
platformTickTimer += Time.deltaTime;
if (platformTickTimer >= platformTickIntervalInSeconds)
{
if (platformTickTimer >= platformTickIntervalInSeconds) {
platformTickTimer = 0;
EOS.Tick();
}
}
}
private void OnApplicationQuit()
{
if (EOS != null)
{
private void OnApplicationQuit() {
if (EOS != null) {
EOS.Release();
EOS = null;
PlatformInterface.Shutdown();
@ -461,4 +431,5 @@ public class EOSSDKComponent : MonoBehaviour
}
#endif
}
}
}

View File

@ -1,3 +1,5 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
@ -8,9 +10,9 @@ using UnityEngine;
/// Create -> EOS -> API Key
/// in order to create an instance of this scriptable object
/// </summary>
[CreateAssetMenu(fileName = "EosApiKey", menuName = "EOS/API Key", order = 1)]
public class EosApiKey : ScriptableObject
{
public class EosApiKey : ScriptableObject {
public string epicProductName = "MyApplication";
public string epicProductVersion = "1.0";
public string epicProductId = "";

View File

@ -1,18 +1,19 @@
using Epic.OnlineServices;
using Epic.OnlineServices.Metrics;
using Epic.OnlineServices.P2P;
using Mirror;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using Epic.OnlineServices.P2P;
using Epic.OnlineServices;
using Mirror;
using Epic.OnlineServices.Metrics;
using System.Collections;
namespace EpicTransport;
namespace EpicTransport {
/// <summary>
/// EOS Transport following the Mirror transport standard
/// </summary>
public class EosTransport : Transport
{
/// <summary>
/// EOS Transport following the Mirror transport standard
/// </summary>
public class EosTransport : Transport {
private const string EPIC_SCHEME = "epic";
private Client client;
@ -40,20 +41,17 @@ public class EosTransport : Transport
private int packetId = 0;
// CHANGED
public Action<string> SetTransportError;
private void Awake()
{
private void Awake() {
Debug.Assert(Channels != null && Channels.Length > 0, "No channel configured for EOS Transport.");
Debug.Assert(Channels.Length < byte.MaxValue, "Too many channels configured for EOS Transport");
if (Channels[0] != PacketReliability.ReliableOrdered)
{
if(Channels[0] != PacketReliability.ReliableOrdered) {
Debug.LogWarning("EOS Transport Channel[0] is not ReliableOrdered, Mirror expects Channel 0 to be ReliableOrdered, only change this if you know what you are doing.");
}
if (Channels[1] != PacketReliability.UnreliableUnordered)
{
if (Channels[1] != PacketReliability.UnreliableUnordered) {
Debug.LogWarning("EOS Transport Channel[1] is not UnreliableUnordered, Mirror expects Channel 1 to be UnreliableUnordered, only change this if you know what you are doing.");
}
@ -61,79 +59,59 @@ public class EosTransport : Transport
StartCoroutine("ChangeRelayStatus");
}
public override void ClientEarlyUpdate()
{
public override void ClientEarlyUpdate() {
EOSSDKComponent.Tick();
if (activeNode != null)
{
if (activeNode != null) {
ignoreCachedMessagesTimer += Time.deltaTime;
if (ignoreCachedMessagesTimer <= ignoreCachedMessagesAtStartUpInSeconds)
{
if (ignoreCachedMessagesTimer <= ignoreCachedMessagesAtStartUpInSeconds) {
activeNode.ignoreAllMessages = true;
}
else
{
} else {
activeNode.ignoreAllMessages = false;
if (client != null && !client.isConnecting)
{
if (EOSSDKComponent.Initialized)
{
if (client != null && !client.isConnecting) {
if (EOSSDKComponent.Initialized) {
client.Connect(client.hostAddress);
}
else
{
} else {
Debug.LogError("EOS not initialized");
client.EosNotInitialized();
}
client.isConnecting = true;
}
}
}
if (enabled)
{
if (enabled) {
activeNode?.ReceiveData();
}
}
public override void ClientLateUpdate() { }
public override void ClientLateUpdate() {}
public override void ServerEarlyUpdate()
{
public override void ServerEarlyUpdate() {
EOSSDKComponent.Tick();
if (activeNode != null)
{
if (activeNode != null) {
ignoreCachedMessagesTimer += Time.deltaTime;
if (ignoreCachedMessagesTimer <= ignoreCachedMessagesAtStartUpInSeconds)
{
if (ignoreCachedMessagesTimer <= ignoreCachedMessagesAtStartUpInSeconds) {
activeNode.ignoreAllMessages = true;
}
else
{
} else {
activeNode.ignoreAllMessages = false;
}
}
if (enabled)
{
if (enabled) {
activeNode?.ReceiveData();
}
}
public override void ServerLateUpdate() { }
public override void ServerLateUpdate() {}
public override bool ClientConnected() => ClientActive() && client.Connected;
public override void ClientConnect(string address)
{
if (!EOSSDKComponent.Initialized)
{
public override void ClientConnect(string address) {
if (!EOSSDKComponent.Initialized) {
Debug.LogError("EOS not initialized. Client could not be started.");
OnClientDisconnected.Invoke();
return;
@ -141,119 +119,96 @@ public class EosTransport : Transport
StartCoroutine("FetchEpicAccountId");
if (ServerActive())
{
if (ServerActive()) {
Debug.LogError("Transport already running as server!");
return;
}
if (!ClientActive() || client.Error)
{
if (!ClientActive() || client.Error) {
Debug.Log($"Starting client, target address {address}.");
client = Client.CreateClient(this, address);
activeNode = client;
if (EOSSDKComponent.CollectPlayerMetrics)
{
if (EOSSDKComponent.CollectPlayerMetrics) {
// Start Metrics colletion session
var sessionOptions = new BeginPlayerSessionOptions();
BeginPlayerSessionOptions sessionOptions = new BeginPlayerSessionOptions();
sessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
sessionOptions.ControllerType = UserControllerType.Unknown;
sessionOptions.DisplayName = EOSSDKComponent.DisplayName;
sessionOptions.GameSessionId = null;
sessionOptions.ServerIp = null;
var result = EOSSDKComponent.GetMetricsInterface().BeginPlayerSession(sessionOptions);
Result result = EOSSDKComponent.GetMetricsInterface().BeginPlayerSession(sessionOptions);
if (result == Result.Success)
{
if(result == Result.Success) {
Debug.Log("Started Metric Session");
}
}
}
else
{
} else {
Debug.LogError("Client already running!");
}
}
public override void ClientConnect(Uri uri)
{
public override void ClientConnect(Uri uri) {
if (uri.Scheme != EPIC_SCHEME)
{
throw new ArgumentException($"Invalid url {uri}, use {EPIC_SCHEME}://EpicAccountId instead", nameof(uri));
}
ClientConnect(uri.Host);
}
public override void ClientSend(ArraySegment<byte> segment, int channelId)
{
public override void ClientSend(ArraySegment<byte> segment, int channelId) {
Send(channelId, segment);
}
public override void ClientDisconnect()
{
if (ClientActive())
{
public override void ClientDisconnect() {
if (ClientActive()) {
Shutdown();
}
}
public bool ClientActive() => client != null;
public override bool ServerActive() => server != null;
public override void ServerStart()
{
if (!EOSSDKComponent.Initialized)
{
public override bool ServerActive() => server != null;
public override void ServerStart() {
if (!EOSSDKComponent.Initialized) {
Debug.LogError("EOS not initialized. Server could not be started.");
return;
}
StartCoroutine("FetchEpicAccountId");
if (ClientActive())
{
if (ClientActive()) {
Debug.LogError("Transport already running as client!");
return;
}
if (!ServerActive())
{
if (!ServerActive()) {
Debug.Log("Starting server.");
server = Server.CreateServer(this, NetworkManager.singleton.maxConnections);
activeNode = server;
if (EOSSDKComponent.CollectPlayerMetrics)
{
if (EOSSDKComponent.CollectPlayerMetrics) {
// Start Metrics colletion session
var sessionOptions = new BeginPlayerSessionOptions();
BeginPlayerSessionOptions sessionOptions = new BeginPlayerSessionOptions();
sessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
sessionOptions.ControllerType = UserControllerType.Unknown;
sessionOptions.DisplayName = EOSSDKComponent.DisplayName;
sessionOptions.GameSessionId = null;
sessionOptions.ServerIp = null;
var result = EOSSDKComponent.GetMetricsInterface().BeginPlayerSession(sessionOptions);
Result result = EOSSDKComponent.GetMetricsInterface().BeginPlayerSession(sessionOptions);
if (result == Result.Success)
{
if (result == Result.Success) {
Debug.Log("Started Metric Session");
}
}
}
else
{
} else {
Debug.LogError("Server already started!");
}
}
public override Uri ServerUri()
{
var epicBuilder = new UriBuilder
{
public override Uri ServerUri() {
UriBuilder epicBuilder = new UriBuilder {
Scheme = EPIC_SCHEME,
Host = EOSSDKComponent.LocalUserProductIdString
};
@ -261,37 +216,32 @@ public class EosTransport : Transport
return epicBuilder.Uri;
}
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
{
if (ServerActive())
{
Send(channelId, segment, connectionId);
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId) {
if (ServerActive()) {
Send( channelId, segment, connectionId);
}
}
public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId);
public override string ServerGetClientAddress(int connectionId) => ServerActive() ? server.ServerGetClientAddress(connectionId) : string.Empty;
public override void ServerStop()
{
if (ServerActive())
{
public override void ServerStop() {
if (ServerActive()) {
Shutdown();
}
}
private void Send(int channelId, ArraySegment<byte> segment, int connectionId = int.MinValue)
{
var packets = GetPacketArray(channelId, segment);
private void Send(int channelId, ArraySegment<byte> segment, int connectionId = int.MinValue) {
Packet[] packets = GetPacketArray(channelId, segment);
for (var i = 0; i < packets.Length; i++)
for(int i = 0; i < packets.Length; i++) {
if (connectionId == int.MinValue) {
if (client == null)
{
if (connectionId == int.MinValue)
{
client.Send(packets[i].ToBytes(), channelId);
OnClientDisconnected.Invoke();
return;
}
else
{
client.Send(packets[i].ToBytes(), channelId);
} else {
server.SendAll(connectionId, packets[i].ToBytes(), channelId);
}
}
@ -299,19 +249,17 @@ public class EosTransport : Transport
packetId++;
}
private Packet[] GetPacketArray(int channelId, ArraySegment<byte> segment)
{
var packetCount = Mathf.CeilToInt((float)segment.Count / (float)GetMaxSinglePacketSize(channelId));
var packets = new Packet[packetCount];
private Packet[] GetPacketArray(int channelId, ArraySegment<byte> segment) {
int packetCount = Mathf.CeilToInt((float) segment.Count / (float)GetMaxSinglePacketSize(channelId));
Packet[] packets = new Packet[packetCount];
for (var i = 0; i < segment.Count; i += GetMaxSinglePacketSize(channelId))
{
var fragment = i / GetMaxSinglePacketSize(channelId);
for (int i = 0; i < segment.Count; i += GetMaxSinglePacketSize(channelId)) {
int fragment = i / GetMaxSinglePacketSize(channelId);
packets[fragment] = new Packet();
packets[fragment].id = packetId;
packets[fragment].fragment = fragment;
packets[fragment].moreFragments = segment.Count - i > GetMaxSinglePacketSize(channelId);
packets[fragment].moreFragments = (segment.Count - i) > GetMaxSinglePacketSize(channelId);
packets[fragment].data = new byte[segment.Count - i > GetMaxSinglePacketSize(channelId) ? GetMaxSinglePacketSize(channelId) : segment.Count - i];
Array.Copy(segment.Array, i, packets[fragment].data, 0, packets[fragment].data.Length);
}
@ -319,17 +267,14 @@ public class EosTransport : Transport
return packets;
}
public override void Shutdown()
{
if (EOSSDKComponent.CollectPlayerMetrics)
{
public override void Shutdown() {
if (EOSSDKComponent.CollectPlayerMetrics) {
// Stop Metrics collection session
var endSessionOptions = new EndPlayerSessionOptions();
EndPlayerSessionOptions endSessionOptions = new EndPlayerSessionOptions();
endSessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
var result = EOSSDKComponent.GetMetricsInterface().EndPlayerSession(endSessionOptions);
Result result = EOSSDKComponent.GetMetricsInterface().EndPlayerSession(endSessionOptions);
if (result == Result.Success)
{
if (result == Result.Success) {
Debug.LogError("Stopped Metric Session");
}
}
@ -349,51 +294,41 @@ public class EosTransport : Transport
public override int GetBatchThreshold(int channelId) => P2PInterface.MaxPacketSize; // Use P2PInterface.MaxPacketSize as everything above will get fragmentated and will be counter effective to batching
public override bool Available()
{
try
{
public override bool Available() {
try {
return EOSSDKComponent.Initialized;
}
catch
{
} catch {
return false;
}
}
private IEnumerator FetchEpicAccountId()
{
while (!EOSSDKComponent.Initialized)
{
private IEnumerator FetchEpicAccountId() {
while (!EOSSDKComponent.Initialized) {
yield return null;
}
productUserId = EOSSDKComponent.LocalUserProductId;
}
private IEnumerator ChangeRelayStatus()
{
while (!EOSSDKComponent.Initialized)
{
private IEnumerator ChangeRelayStatus() {
while (!EOSSDKComponent.Initialized) {
yield return null;
}
var setRelayControlOptions = new SetRelayControlOptions();
SetRelayControlOptions setRelayControlOptions = new SetRelayControlOptions();
setRelayControlOptions.RelayControl = relayControl;
EOSSDKComponent.GetP2PInterface().SetRelayControl(setRelayControlOptions);
}
public void ResetIgnoreMessagesAtStartUpTimer()
{
public void ResetIgnoreMessagesAtStartUpTimer() {
ignoreCachedMessagesTimer = 0;
}
private void OnDestroy()
{
if (activeNode != null)
{
private void OnDestroy() {
if (activeNode != null) {
Shutdown();
}
}
}
}

View File

@ -1,15 +1,14 @@
using Epic.OnlineServices.Logging;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace EpicTransport;
namespace EpicTransport {
public static class Logger {
public static class Logger
{
public static void EpicDebugLog(LogMessage message)
{
switch (message.Level)
{
public static void EpicDebugLog(LogMessage message) {
switch (message.Level) {
case LogLevel.Info:
Debug.Log($"Epic Manager: Category - {message.Category} Message - {message.Message}");
break;
@ -27,4 +26,5 @@ public static class Logger
break;
}
}
}
}

View File

@ -1,9 +1,7 @@
using System;
namespace EpicTransport;
public struct Packet
{
namespace EpicTransport {
public struct Packet {
public const int headerSize = sizeof(uint) + sizeof(uint) + 1;
public int size => headerSize + data.Length;
@ -15,21 +13,20 @@ public struct Packet
// body
public byte[] data;
public byte[] ToBytes()
{
var array = new byte[size];
public byte[] ToBytes() {
byte[] array = new byte[size];
// Copy id
array[0] = (byte)id;
array[1] = (byte)(id >> 8);
array[2] = (byte)(id >> 0x10);
array[3] = (byte)(id >> 0x18);
array[0] = (byte) id;
array[1] = (byte) (id >> 8);
array[2] = (byte) (id >> 0x10);
array[3] = (byte) (id >> 0x18);
// Copy fragment
array[4] = (byte)fragment;
array[5] = (byte)(fragment >> 8);
array[6] = (byte)(fragment >> 0x10);
array[7] = (byte)(fragment >> 0x18);
array[4] = (byte) fragment;
array[5] = (byte) (fragment >> 8);
array[6] = (byte) (fragment >> 0x10);
array[7] = (byte) (fragment >> 0x18);
array[8] = moreFragments ? (byte)1 : (byte)0;
@ -38,8 +35,7 @@ public struct Packet
return array;
}
public void FromBytes(byte[] array)
{
public void FromBytes(byte[] array) {
id = BitConverter.ToInt32(array, 0);
fragment = BitConverter.ToInt32(array, 4);
moreFragments = array[8] == 1;
@ -47,4 +43,5 @@ public struct Packet
data = new byte[array.Length - 9];
Array.Copy(array, 9, data, 0, data.Length);
}
}
}

View File

@ -1,14 +1,13 @@
using System;
using System.Text;
public class RandomString
{
public class RandomString {
// Generates a random string with a given size.
public static string Generate(int size)
{
public static string Generate(int size) {
var builder = new StringBuilder(size);
var random = new Random();
Random random = new Random();
// Unicode/ASCII Letters are divided into two blocks
// (Letters 6590 / 97122):
@ -16,23 +15,19 @@ public class RandomString
// the second group containing the lowercase.
// char is a single Unicode character
var offsetLowerCase = 'a';
var offsetUpperCase = 'A';
char offsetLowerCase = 'a';
char offsetUpperCase = 'A';
const int lettersOffset = 26; // A...Z or a..z: length=26
for (var i = 0; i < size; i++)
{
for (var i = 0; i < size; i++) {
char offset;
if (random.Next(0, 2) == 0)
{
if(random.Next(0,2) == 0) {
offset = offsetLowerCase;
}
else
{
} else {
offset = offsetUpperCase;
}
var @char = (char)random.Next(offset, offset + lettersOffset);
var @char = (char) random.Next(offset, offset + lettersOffset);
builder.Append(@char);
}

View File

@ -4,10 +4,8 @@ using System;
using System.Collections.Generic;
using UnityEngine;
namespace EpicTransport;
public class Server : Common
{
namespace EpicTransport {
public class Server : Common {
private event Action<int> OnConnected;
private event Action<int, byte[], int> OnReceivedData;
private event Action<int> OnDisconnected;
@ -18,65 +16,55 @@ public class Server : Common
private int maxConnections;
private int nextConnectionID;
public static Server CreateServer(EosTransport transport, int maxConnections)
{
var s = new Server(transport, maxConnections);
public static Server CreateServer(EosTransport transport, int maxConnections) {
Server s = new Server(transport, maxConnections);
s.OnConnected += (id) => transport.OnServerConnected.Invoke(id);
s.OnDisconnected += (id) => transport.OnServerDisconnected.Invoke(id);
s.OnReceivedData += (id, data, channel) => transport.OnServerDataReceived.Invoke(id, new ArraySegment<byte>(data), channel);
s.OnReceivedError += (id, exception) => transport.OnServerError.Invoke(id, exception);
// CHANGED
s.OnReceivedError += (id, exception) => transport.OnServerError?.Invoke(id, Mirror.TransportError.Unexpected, exception.ToString());
if (!EOSSDKComponent.Initialized)
{
if (!EOSSDKComponent.Initialized) {
Debug.LogError("EOS not initialized.");
}
return s;
}
private Server(EosTransport transport, int maxConnections) : base(transport)
{
private Server(EosTransport transport, int maxConnections) : base(transport) {
this.maxConnections = maxConnections;
epicToMirrorIds = new BidirectionalDictionary<ProductUserId, int>();
epicToSocketIds = new Dictionary<ProductUserId, SocketId>();
nextConnectionID = 1;
}
protected override void OnNewConnection(OnIncomingConnectionRequestInfo result)
{
if (ignoreAllMessages)
{
protected override void OnNewConnection(OnIncomingConnectionRequestInfo result) {
if (ignoreAllMessages) {
return;
}
if (deadSockets.Contains(result.SocketId.SocketName))
{
if (deadSockets.Contains(result.SocketId.SocketName)) {
Debug.LogError("Received incoming connection request from dead socket");
return;
}
EOSSDKComponent.GetP2PInterface().AcceptConnection(
new AcceptConnectionOptions()
{
new AcceptConnectionOptions() {
LocalUserId = EOSSDKComponent.LocalUserProductId,
RemoteUserId = result.RemoteUserId,
SocketId = result.SocketId
});
}
protected override void OnReceiveInternalData(InternalMessages type, ProductUserId clientUserId, SocketId socketId)
{
if (ignoreAllMessages)
{
protected override void OnReceiveInternalData(InternalMessages type, ProductUserId clientUserId, SocketId socketId) {
if (ignoreAllMessages) {
return;
}
switch (type)
{
switch (type) {
case InternalMessages.CONNECT:
if (epicToMirrorIds.Count >= maxConnections)
{
if (epicToMirrorIds.Count >= maxConnections) {
Debug.LogError("Reached max connections");
//CloseP2PSessionWithUser(clientUserId, socketId);
SendInternal(clientUserId, socketId, InternalMessages.DISCONNECT);
@ -85,7 +73,7 @@ public class Server : Common
SendInternal(clientUserId, socketId, InternalMessages.ACCEPT_CONNECT);
var connectionId = nextConnectionID++;
int connectionId = nextConnectionID++;
epicToMirrorIds.Add(clientUserId, connectionId);
epicToSocketIds.Add(clientUserId, socketId);
OnConnected.Invoke(connectionId);
@ -95,16 +83,13 @@ public class Server : Common
Debug.Log($"Client with Product User ID {clientUserIdString} connected. Assigning connection id {connectionId}");
break;
case InternalMessages.DISCONNECT:
if (epicToMirrorIds.TryGetValue(clientUserId, out var connId))
{
if (epicToMirrorIds.TryGetValue(clientUserId, out int connId)) {
OnDisconnected.Invoke(connId);
//CloseP2PSessionWithUser(clientUserId, socketId);
epicToMirrorIds.Remove(clientUserId);
epicToSocketIds.Remove(clientUserId);
Debug.Log($"Client with Product User ID {clientUserId} disconnected.");
}
else
{
} else {
OnReceivedError.Invoke(-1, new Exception("ERROR Unknown Product User ID"));
}
@ -115,19 +100,14 @@ public class Server : Common
}
}
protected override void OnReceiveData(byte[] data, ProductUserId clientUserId, int channel)
{
if (ignoreAllMessages)
{
protected override void OnReceiveData(byte[] data, ProductUserId clientUserId, int channel) {
if (ignoreAllMessages) {
return;
}
if (epicToMirrorIds.TryGetValue(clientUserId, out var connectionId))
{
if (epicToMirrorIds.TryGetValue(clientUserId, out int connectionId)) {
OnReceivedData.Invoke(connectionId, data, channel);
}
else
{
} else {
SocketId socketId;
epicToSocketIds.TryGetValue(clientUserId, out socketId);
CloseP2PSessionWithUser(clientUserId, socketId);
@ -140,26 +120,20 @@ public class Server : Common
}
}
public void Disconnect(int connectionId)
{
if (epicToMirrorIds.TryGetValue(connectionId, out var userId))
{
public void Disconnect(int connectionId) {
if (epicToMirrorIds.TryGetValue(connectionId, out ProductUserId userId)) {
SocketId socketId;
epicToSocketIds.TryGetValue(userId, out socketId);
SendInternal(userId, socketId, InternalMessages.DISCONNECT);
epicToMirrorIds.Remove(userId);
epicToSocketIds.Remove(userId);
}
else
{
} else {
Debug.LogWarning("Trying to disconnect unknown connection id: " + connectionId);
}
}
public void Shutdown()
{
foreach (KeyValuePair<ProductUserId, int> client in epicToMirrorIds)
{
public void Shutdown() {
foreach (KeyValuePair<ProductUserId, int> client in epicToMirrorIds) {
Disconnect(client.Value);
SocketId socketId;
epicToSocketIds.TryGetValue(client.Key, out socketId);
@ -172,49 +146,41 @@ public class Server : Common
Dispose();
}
public void SendAll(int connectionId, byte[] data, int channelId)
{
if (epicToMirrorIds.TryGetValue(connectionId, out var userId))
{
public void SendAll(int connectionId, byte[] data, int channelId) {
if (epicToMirrorIds.TryGetValue(connectionId, out ProductUserId userId)) {
SocketId socketId;
epicToSocketIds.TryGetValue(userId, out socketId);
Send(userId, socketId, data, (byte)channelId);
}
else
{
} else {
Debug.LogError("Trying to send on unknown connection: " + connectionId);
OnReceivedError.Invoke(connectionId, new Exception("ERROR Unknown Connection"));
}
}
public string ServerGetClientAddress(int connectionId)
{
if (epicToMirrorIds.TryGetValue(connectionId, out var userId))
{
public string ServerGetClientAddress(int connectionId) {
if (epicToMirrorIds.TryGetValue(connectionId, out ProductUserId userId)) {
string userIdString;
userId.ToString(out userIdString);
return userIdString;
}
else
{
} else {
Debug.LogError("Trying to get info on unknown connection: " + connectionId);
OnReceivedError.Invoke(connectionId, new Exception("ERROR Unknown Connection"));
return string.Empty;
}
}
protected override void OnConnectionFailed(ProductUserId remoteId)
{
if (ignoreAllMessages)
{
protected override void OnConnectionFailed(ProductUserId remoteId) {
if (ignoreAllMessages) {
return;
}
var connectionId = epicToMirrorIds.TryGetValue(remoteId, out var connId) ? connId : nextConnectionID++;
int connectionId = epicToMirrorIds.TryGetValue(remoteId, out int connId) ? connId : nextConnectionID++;
OnDisconnected.Invoke(connectionId);
Debug.LogError("Connection Failed, removing user");
epicToMirrorIds.Remove(remoteId);
epicToSocketIds.Remove(remoteId);
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -12,8 +12,18 @@ namespace Mirror.Weaver
? td.GetElementType().FullName == type.FullName
: td.FullName == type.FullName;
// check if 'td' is exactly of type T.
// it does not check if any base type is of <T>, only the specific type.
// for example:
// NetworkConnection Is NetworkConnection: true
// NetworkConnectionToClient Is NetworkConnection: false
public static bool Is<T>(this TypeReference td) => Is(td, typeof(T));
// check if 'tr' is derived from T.
// it does not check if 'tr' is exactly T.
// for example:
// NetworkConnection IsDerivedFrom<NetworkConnection>: false
// NetworkConnectionToClient IsDerivedFrom<NetworkConnection>: true
public static bool IsDerivedFrom<T>(this TypeReference tr) => IsDerivedFrom(tr, typeof(T));
public static bool IsDerivedFrom(this TypeReference tr, Type baseClass)
@ -79,7 +89,10 @@ namespace Mirror.Weaver
public static bool IsNetworkIdentityField(this TypeReference tr) =>
tr.Is<UnityEngine.GameObject>() ||
tr.Is<NetworkIdentity>() ||
tr.IsDerivedFrom<NetworkBehaviour>();
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
tr.IsDerivedFrom<NetworkBehaviour>() ||
tr.Is<NetworkBehaviour>();
public static bool CanBeResolved(this TypeReference parent)
{

View File

@ -10,10 +10,11 @@ namespace Mirror.Weaver
// generates code like:
public void CmdThrust(float thrusting, int spin)
{
NetworkWriter networkWriter = new NetworkWriter();
networkWriter.Write(thrusting);
networkWriter.WritePackedUInt32((uint)spin);
base.SendCommandInternal(cmdName, networkWriter, channel);
NetworkWriterPooled writer = NetworkWriterPool.Get();
writer.Write(thrusting);
writer.WritePackedUInt32((uint)spin);
base.SendCommandInternal(cmdName, cmdHash, writer, channel);
NetworkWriterPool.Return(writer);
}
public void CallCmdThrust(float thrusting, int spin)
@ -38,7 +39,7 @@ namespace Mirror.Weaver
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
// NetworkWriter writer = new NetworkWriter();
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes);
// write all the arguments that the user passed to the Cmd call
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.Command, ref WeavingFailed))
@ -52,6 +53,11 @@ namespace Mirror.Weaver
worker.Emit(OpCodes.Ldarg_0);
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
worker.Emit(OpCodes.Ldstr, md.FullName);
// pass the function hash so we don't have to compute it at runtime
// otherwise each GetStableHash call requires O(N) complexity.
// noticeable for long function names:
// https://github.com/MirrorNetworking/Mirror/issues/3375
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
// writer
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, channel);
@ -59,7 +65,7 @@ namespace Mirror.Weaver
worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Call, weaverTypes.sendCommandInternal);
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes);
worker.Emit(OpCodes.Ret);
return cmd;

View File

@ -137,21 +137,21 @@ namespace Mirror.Weaver
public static void WriteSetupLocals(ILProcessor worker, WeaverTypes weaverTypes)
{
worker.Body.InitLocals = true;
worker.Body.Variables.Add(new VariableDefinition(weaverTypes.Import<PooledNetworkWriter>()));
worker.Body.Variables.Add(new VariableDefinition(weaverTypes.Import<NetworkWriterPooled>()));
}
public static void WriteCreateWriter(ILProcessor worker, WeaverTypes weaverTypes)
public static void WriteGetWriter(ILProcessor worker, WeaverTypes weaverTypes)
{
// create writer
worker.Emit(OpCodes.Call, weaverTypes.GetPooledWriterReference);
worker.Emit(OpCodes.Call, weaverTypes.GetWriterReference);
worker.Emit(OpCodes.Stloc_0);
}
public static void WriteRecycleWriter(ILProcessor worker, WeaverTypes weaverTypes)
public static void WriteReturnWriter(ILProcessor worker, WeaverTypes weaverTypes)
{
// NetworkWriterPool.Recycle(writer);
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Call, weaverTypes.RecycleWriterReference);
worker.Emit(OpCodes.Call, weaverTypes.ReturnWriterReference);
}
public static bool WriteArguments(ILProcessor worker, Writers writers, Logger Log, MethodDefinition method, RemoteCallType callType, ref bool WeavingFailed)
@ -397,7 +397,7 @@ namespace Mirror.Weaver
MethodDefinition serialize = new MethodDefinition(SerializeMethodName,
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
weaverTypes.Import<bool>());
weaverTypes.Import(typeof(void)));
serialize.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, weaverTypes.Import<NetworkWriter>()));
serialize.Parameters.Add(new ParameterDefinition("forceAll", ParameterAttributes.None, weaverTypes.Import<bool>()));
@ -405,10 +405,7 @@ namespace Mirror.Weaver
serialize.Body.InitLocals = true;
// loc_0, this local variable is to determine if any variable was dirty
VariableDefinition dirtyLocal = new VariableDefinition(weaverTypes.Import<bool>());
serialize.Body.Variables.Add(dirtyLocal);
// base.SerializeSyncVars(writer, forceAll);
MethodReference baseSerialize = Resolvers.TryResolveMethodInParents(netBehaviourSubclass.BaseType, assembly, SerializeMethodName);
if (baseSerialize != null)
{
@ -419,16 +416,20 @@ namespace Mirror.Weaver
// forceAll
worker.Emit(OpCodes.Ldarg_2);
worker.Emit(OpCodes.Call, baseSerialize);
// set dirtyLocal to result of base.OnSerialize()
worker.Emit(OpCodes.Stloc_0);
}
// Generates: if (forceAll);
// Generates:
// if (forceAll)
// {
// writer.WriteInt(health);
// ...
// }
Instruction initialStateLabel = worker.Create(OpCodes.Nop);
// forceAll
worker.Emit(OpCodes.Ldarg_2);
worker.Emit(OpCodes.Brfalse, initialStateLabel);
worker.Emit(OpCodes.Ldarg_2); // load 'forceAll' flag
worker.Emit(OpCodes.Brfalse, initialStateLabel); // start the 'if forceAll' branch
// generates write.Write(syncVar) for each SyncVar in forceAll case
foreach (FieldDefinition syncVarDef in syncVars)
{
FieldReference syncVar = syncVarDef;
@ -442,7 +443,21 @@ namespace Mirror.Weaver
// this
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, syncVar);
MethodReference writeFunc = writers.GetWriteFunc(syncVar.FieldType, ref WeavingFailed);
MethodReference writeFunc;
// For NBs we always need to use the default NetworkBehaviour write func
// since the reader counter part uses that exact layout which is not easy to change
// without introducing more edge cases
// effectively this disallows custom NB-type writers/readers on SyncVars
// see: https://github.com/MirrorNetworking/Mirror/issues/2680
if (syncVar.FieldType.IsDerivedFrom<NetworkBehaviour>())
{
writeFunc = writers.GetWriteFunc(weaverTypes.Import<NetworkBehaviour>(), ref WeavingFailed);
}
else
{
writeFunc = writers.GetWriteFunc(syncVar.FieldType, ref WeavingFailed);
}
if (writeFunc != null)
{
worker.Emit(OpCodes.Call, writeFunc);
@ -455,15 +470,14 @@ namespace Mirror.Weaver
}
}
// always return true if forceAll
// Generates: return true
worker.Emit(OpCodes.Ldc_I4_1);
// if (forceAll) then always return at the end of the 'if' case
worker.Emit(OpCodes.Ret);
// Generates: end if (forceAll);
// end the 'if' case for "if (forceAll)"
worker.Append(initialStateLabel);
////////////////////////////////////////////////////////////////////
// write dirty bits before the data fields
// Generates: writer.WritePackedUInt64 (base.get_syncVarDirtyBits ());
// writer
@ -480,7 +494,6 @@ namespace Mirror.Weaver
int dirtyBit = syncVarAccessLists.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName);
foreach (FieldDefinition syncVarDef in syncVars)
{
FieldReference syncVar = syncVarDef;
if (netBehaviourSubclass.HasGenericParameters)
{
@ -504,7 +517,21 @@ namespace Mirror.Weaver
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, syncVar);
MethodReference writeFunc = writers.GetWriteFunc(syncVar.FieldType, ref WeavingFailed);
MethodReference writeFunc;
// For NBs we always need to use the default NetworkBehaviour write func
// since the reader counter part uses that exact layout which is not easy to change
// without introducing more edge cases
// effectively this disallows custom NB-type writers/readers on SyncVars
// see: https://github.com/MirrorNetworking/Mirror/issues/2680
if (syncVar.FieldType.IsDerivedFrom<NetworkBehaviour>())
{
writeFunc = writers.GetWriteFunc(weaverTypes.Import<NetworkBehaviour>(), ref WeavingFailed);
}
else
{
writeFunc = writers.GetWriteFunc(syncVar.FieldType, ref WeavingFailed);
}
if (writeFunc != null)
{
worker.Emit(OpCodes.Call, writeFunc);
@ -516,11 +543,6 @@ namespace Mirror.Weaver
return;
}
// something was dirty
worker.Emit(OpCodes.Ldc_I4_1);
// set dirtyLocal to true
worker.Emit(OpCodes.Stloc_0);
worker.Append(varLabel);
dirtyBit += 1;
}
@ -529,8 +551,7 @@ namespace Mirror.Weaver
//worker.Emit(OpCodes.Ldstr, $"Injected Serialize {netBehaviourSubclass.Name}");
//worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference);
// generate: return dirtyLocal
worker.Emit(OpCodes.Ldloc_0);
// generate: return
worker.Emit(OpCodes.Ret);
netBehaviourSubclass.Methods.Add(serialize);
}
@ -589,10 +610,9 @@ namespace Mirror.Weaver
worker.Emit(OpCodes.Ldflda, netIdField);
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarDeserialize_NetworkIdentity);
}
// TODO this only uses the persistent netId for types DERIVED FROM NB.
// not if the type is just 'NetworkBehaviour'.
// this is what original implementation did too. fix it after.
else if (syncVar.FieldType.IsDerivedFrom<NetworkBehaviour>())
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
else if (syncVar.FieldType.IsDerivedFrom<NetworkBehaviour>() || syncVar.FieldType.Is<NetworkBehaviour>())
{
// reader
worker.Emit(OpCodes.Ldarg_1);

View File

@ -68,7 +68,7 @@ namespace Mirror.Weaver
//worker.Emit(OpCodes.Ldstr, $"Call ClientRpc function {md.Name}");
//worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference);
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes);
// write all the arguments that the user passed to the Rpc call
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.ClientRpc, ref WeavingFailed))
@ -82,6 +82,11 @@ namespace Mirror.Weaver
worker.Emit(OpCodes.Ldarg_0);
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
worker.Emit(OpCodes.Ldstr, md.FullName);
// pass the function hash so we don't have to compute it at runtime
// otherwise each GetStableHash call requires O(N) complexity.
// noticeable for long function names:
// https://github.com/MirrorNetworking/Mirror/issues/3375
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
// writer
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, channel);
@ -89,7 +94,7 @@ namespace Mirror.Weaver
worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Callvirt, weaverTypes.sendRpcInternal);
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes);
worker.Emit(OpCodes.Ret);

View File

@ -16,7 +16,7 @@ namespace Mirror.Weaver
foreach (FieldDefinition fd in td.Fields)
{
if (fd.FieldType.IsGenericParameter)
if (fd.FieldType.IsGenericParameter || fd.ContainsGenericParameter)
{
// can't call .Resolve on generic ones
continue;

View File

@ -203,7 +203,9 @@ namespace Mirror.Weaver
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarNetworkIdentityReference);
worker.Emit(OpCodes.Ret);
}
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>() || fd.FieldType.Is<NetworkBehaviour>())
{
// return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId);
// this.
@ -331,10 +333,9 @@ namespace Mirror.Weaver
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_NetworkIdentity);
}
// TODO this only uses the persistent netId for types DERIVED FROM NB.
// not if the type is just 'NetworkBehaviour'.
// this is what original implementation did too. fix it after.
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>() || fd.FieldType.Is<NetworkBehaviour>())
{
// NetworkIdentity setter needs one more parameter: netId field ref
// (actually its a NetworkBehaviourSyncVar type)
@ -368,11 +369,13 @@ namespace Mirror.Weaver
// GameObject/NetworkIdentity SyncVars have a new field for netId
FieldDefinition netIdField = null;
// NetworkBehaviour has different field type than other NetworkIdentityFields
if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>() || fd.FieldType.Is<NetworkBehaviour>())
{
netIdField = new FieldDefinition($"___{fd.Name}NetId",
FieldAttributes.Family, // needs to be protected for generic classes, otherwise access isn't allowed
weaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
weaverTypes.Import<NetworkBehaviourSyncVar>());
netIdField.DeclaringType = td;
syncVarNetIds[fd] = netIdField;
@ -475,7 +478,11 @@ namespace Mirror.Weaver
{
td.Fields.Add(fd);
}
syncVarAccessLists.SetNumSyncVars(td.FullName, syncVars.Count);
// include parent class syncvars
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3457
int parentSyncVarCount = syncVarAccessLists.GetSyncVarStart(td.BaseType.FullName);
syncVarAccessLists.SetNumSyncVars(td.FullName, parentSyncVarCount + syncVars.Count);
return (syncVars, syncVarNetIds);
}

View File

@ -9,8 +9,16 @@ namespace Mirror.Weaver
// helper functions to check if the method has a NetworkConnection parameter
public static bool HasNetworkConnectionParameter(MethodDefinition md)
{
return md.Parameters.Count > 0 &&
md.Parameters[0].ParameterType.Is<NetworkConnection>();
if (md.Parameters.Count > 0)
{
// we need to allow both NetworkConnection, and inheriting types.
// NetworkBehaviour.SendTargetRpc takes a NetworkConnection parameter.
// fixes https://github.com/vis2k/Mirror/issues/3290
TypeReference type = md.Parameters[0].ParameterType;
return type.Is<NetworkConnection>() ||
type.IsDerivedFrom<NetworkConnection>();
}
return false;
}
public static MethodDefinition ProcessTargetRpcInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
@ -34,16 +42,25 @@ namespace Mirror.Weaver
// NetworkConnection parameter is optional
if (HasNetworkConnectionParameter(md))
{
// on server, the NetworkConnection parameter is a connection to client.
// when the rpc is invoked on the client, it still has the same
// function signature. we pass in the connection to server,
// which is cleaner than just passing null)
//NetworkClient.readyconnection
// TargetRpcs are sent from server to client.
// on server, we currently support two types:
// TargetRpc(NetworkConnection)
// TargetRpc(NetworkConnectionToClient)
// however, it's always a connection to client.
// in the future, only NetworkConnectionToClient will be supported.
// explicit typing helps catch issues at compile time.
//
// TODO
// a) .connectionToServer = best solution. no doubt.
// b) NetworkClient.connection for now. add TODO to not use static later.
worker.Emit(OpCodes.Call, weaverTypes.NetworkClientConnectionReference);
// on client, InvokeTargetRpc calls the original code.
// we need to fill in the NetworkConnection parameter.
// NetworkClient.connection is always a connection to server.
//
// we used to pass NetworkClient.connection as the TargetRpc parameter.
// which caused: https://github.com/MirrorNetworking/Mirror/issues/3455
// when the parameter is defined as a NetworkConnectionToClient.
//
// a client's connection never fits into a NetworkConnectionToClient.
// we need to always pass null here.
worker.Emit(OpCodes.Ldnull);
}
// process reader parameters and skip first one if first one is NetworkConnection
@ -100,7 +117,7 @@ namespace Mirror.Weaver
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes);
// write all the arguments that the user passed to the TargetRpc call
// (skip first one if first one is NetworkConnection)
@ -122,12 +139,17 @@ namespace Mirror.Weaver
}
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
worker.Emit(OpCodes.Ldstr, md.FullName);
// pass the function hash so we don't have to compute it at runtime
// otherwise each GetStableHash call requires O(N) complexity.
// noticeable for long function names:
// https://github.com/MirrorNetworking/Mirror/issues/3375
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
// writer
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0));
worker.Emit(OpCodes.Callvirt, weaverTypes.sendTargetRpcInternal);
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes);
worker.Emit(OpCodes.Ret);

View File

@ -19,6 +19,7 @@ namespace Mirror.Weaver
AssemblyDefinition assembly;
WeaverTypes weaverTypes;
TypeDefinition GeneratedCodeClass;
// CHANGED
internal Logger Log;
Dictionary<TypeReference, MethodReference> readFuncs =
@ -114,7 +115,9 @@ namespace Mirror.Weaver
return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList), ref WeavingFailed);
}
else if (variableReference.IsDerivedFrom<NetworkBehaviour>())
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
else if (variableReference.IsDerivedFrom<NetworkBehaviour>() || variableReference.Is<NetworkBehaviour>())
{
return GetNetworkBehaviourReader(variableReference);
}
@ -138,6 +141,7 @@ namespace Mirror.Weaver
WeavingFailed = true;
return null;
}
// CHANGED
/*
if (variableDefinition.HasGenericParameters)
{
@ -270,6 +274,7 @@ namespace Mirror.Weaver
GenerateNullCheck(worker, ref WeavingFailed);
CreateNew(variable, worker, td, ref WeavingFailed);
// CHANGED
this.ReadAllFieldsGeneric(variable, worker, ref WeavingFailed);
worker.Emit(OpCodes.Ldloc_0);

View File

@ -25,6 +25,12 @@ namespace Mirror.Weaver
AssemblyDefinition CurrentAssembly;
Writers writers;
Readers readers;
// in case of weaver errors, we don't stop immediately.
// we log all errors and then eventually return false if
// weaving has failed.
// this way the user can fix multiple errors at once, instead of having
// to fix -> recompile -> fix -> recompile for one error at a time.
bool WeavingFailed;
// logger functions can be set from the outside.
@ -200,6 +206,7 @@ namespace Mirror.Weaver
ModuleDefinition moduleDefinition = CurrentAssembly.MainModule;
Console.WriteLine($"Script Module: {moduleDefinition.Name}");
// CHANGED
QSBReaderWriterProcessor.Process(moduleDefinition, writers, readers, ref WeavingFailed);
modified |= WeaveModule(moduleDefinition);

View File

@ -11,8 +11,8 @@ namespace Mirror.Weaver
public MethodReference ScriptableObjectCreateInstanceMethod;
public MethodReference NetworkBehaviourDirtyBitsReference;
public MethodReference GetPooledWriterReference;
public MethodReference RecycleWriterReference;
public MethodReference GetWriterReference;
public MethodReference ReturnWriterReference;
public MethodReference NetworkClientConnectionReference;
@ -77,28 +77,14 @@ namespace Mirror.Weaver
TypeReference NetworkServerType = Import(typeof(NetworkServer));
NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_active", ref WeavingFailed);
TypeReference NetworkClientType = Import(typeof(NetworkClient));
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_active", ref WeavingFailed);
TypeReference RemoteCallDelegateType = Import<RemoteCalls.RemoteCallDelegate>();
RemoteCallDelegateConstructor = Resolvers.ResolveMethod(RemoteCallDelegateType, assembly, Log, ".ctor", ref WeavingFailed);
NetworkClientConnectionReference = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_connection", ref WeavingFailed);
TypeReference NetworkBehaviourType = Import<NetworkBehaviour>();
TypeReference RemoteProcedureCallsType = Import(typeof(RemoteCalls.RemoteProcedureCalls));
TypeReference ScriptableObjectType = Import<ScriptableObject>();
ScriptableObjectCreateInstanceMethod = Resolvers.ResolveMethod(
ScriptableObjectType, assembly, Log,
md => md.Name == "CreateInstance" && md.HasGenericParameters,
ref WeavingFailed);
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, assembly, "syncVarDirtyBits");
TypeReference NetworkWriterPoolType = Import(typeof(NetworkWriterPool));
GetPooledWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "GetWriter", ref WeavingFailed);
RecycleWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Recycle", ref WeavingFailed);
NetworkClientConnectionReference = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_connection", ref WeavingFailed);
generatedSyncVarSetter = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter", ref WeavingFailed);
generatedSyncVarSetter_GameObject = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_GameObject", ref WeavingFailed);
@ -114,9 +100,25 @@ namespace Mirror.Weaver
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkIdentity", ref WeavingFailed);
getSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkBehaviour", ref WeavingFailed);
sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendCommandInternal", ref WeavingFailed);
sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendRPCInternal", ref WeavingFailed);
sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendTargetRPCInternal", ref WeavingFailed);
InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "InitSyncObject", ref WeavingFailed);
TypeReference RemoteProcedureCallsType = Import(typeof(RemoteCalls.RemoteProcedureCalls));
registerCommandReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterCommand", ref WeavingFailed);
registerRpcReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterRpc", ref WeavingFailed);
TypeReference RemoteCallDelegateType = Import<RemoteCalls.RemoteCallDelegate>();
RemoteCallDelegateConstructor = Resolvers.ResolveMethod(RemoteCallDelegateType, assembly, Log, ".ctor", ref WeavingFailed);
TypeReference ScriptableObjectType = Import<ScriptableObject>();
ScriptableObjectCreateInstanceMethod = Resolvers.ResolveMethod(
ScriptableObjectType, assembly, Log,
md => md.Name == "CreateInstance" && md.HasGenericParameters,
ref WeavingFailed);
TypeReference unityDebug = Import(typeof(UnityEngine.Debug));
// these have multiple methods with same name, so need to check parameters too
logErrorReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
@ -133,11 +135,10 @@ namespace Mirror.Weaver
TypeReference typeType = Import(typeof(Type));
getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, assembly, Log, "GetTypeFromHandle", ref WeavingFailed);
sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendCommandInternal", ref WeavingFailed);
sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendRPCInternal", ref WeavingFailed);
sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendTargetRPCInternal", ref WeavingFailed);
InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "InitSyncObject", ref WeavingFailed);
TypeReference NetworkWriterPoolType = Import(typeof(NetworkWriterPool));
GetWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Get", ref WeavingFailed);
ReturnWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Return", ref WeavingFailed);
TypeReference readerExtensions = Import(typeof(NetworkReaderExtensions));
readNetworkBehaviourGeneric = Resolvers.ResolveMethod(readerExtensions, assembly, Log, (md =>
@ -147,6 +148,7 @@ namespace Mirror.Weaver
}),
ref WeavingFailed);
// CHANGED
/*
// [InitializeOnLoadMethod]
// 'UnityEditor' is not available in builds.

View File

@ -116,7 +116,9 @@ namespace Mirror.Weaver
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList), ref WeavingFailed);
}
if (variableReference.IsDerivedFrom<NetworkBehaviour>())
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
if (variableReference.IsDerivedFrom<NetworkBehaviour>() || variableReference.Is<NetworkBehaviour>())
{
return GetNetworkBehaviourWriter(variableReference);
}
@ -139,6 +141,7 @@ namespace Mirror.Weaver
{
throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
}
// CHANGED
/*
if (variableDefinition.HasGenericParameters)
{
@ -219,6 +222,7 @@ namespace Mirror.Weaver
if (!variable.Resolve().IsValueType)
WriteNullCheck(worker, ref WeavingFailed);
// CHANGED
if (!this.WriteAllFieldsGeneric(variable, worker, ref WeavingFailed))
return null;

View File

@ -67,7 +67,7 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
private string _lastTransportError;
private static readonly string[] _kcpErrorLogs =
{
"KCP: received disconnect message",
"KcpPeer: received disconnect message",
"Failed to resolve host: .*"
};
@ -110,7 +110,7 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
QSBCore.ProfileManager.OnProfileSignInComplete += _ => InitPlayerName();
playerPrefab = QSBCore.NetworkAssetBundle.LoadAsset<GameObject>("Assets/Prefabs/NETWORK_Player_Body.prefab");
playerPrefab.GetRequiredComponent<NetworkIdentity>().SetValue("m_AssetId", 1.ToGuid().ToString("N"));
playerPrefab.GetRequiredComponent<NetworkIdentity>().SetValue("_assetId", (uint)1);
ShipPrefab = MakeNewNetworkObject(2, "NetworkShip", typeof(ShipTransformSync));
var shipVector3Sync = ShipPrefab.AddComponent<Vector3VariableSyncer>();
@ -202,7 +202,7 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
/// this works by calling Unload(false) and then reloading the AssetBundle,
/// which makes LoadAsset give you a new resource.
/// see https://docs.unity3d.com/Manual/AssetBundles-Native.html.
private static GameObject MakeNewNetworkObject(int assetId, string name, Type networkBehaviourType)
private static GameObject MakeNewNetworkObject(uint assetId, string name, Type networkBehaviourType)
{
var bundle = QSBCore.Helper.Assets.LoadBundle("AssetBundles/qsb_empty");
@ -216,7 +216,7 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
bundle.Unload(false);
template.name = name;
template.AddComponent<NetworkIdentity>().SetValue("m_AssetId", assetId.ToGuid().ToString("N"));
template.AddComponent<NetworkIdentity>().SetValue("_assetId", assetId);
template.AddComponent(networkBehaviourType);
return template;
}

View File

@ -59,7 +59,7 @@ public abstract class QSBNetworkBehaviour : NetworkBehaviour
return;
}
using var writer = NetworkWriterPool.GetWriter();
using var writer = NetworkWriterPool.Get();
Serialize(writer);
UpdatePrevData();
@ -127,7 +127,7 @@ public abstract class QSBNetworkBehaviour : NetworkBehaviour
Array.Copy(data.Array!, data.Offset, _lastKnownData, 0, data.Count);
}
using var reader = NetworkReaderPool.GetReader(data);
using var reader = NetworkReaderPool.Get(data);
Deserialize(reader);
}
}

Binary file not shown.