perf(transport): BREAKING: callbacks instead of unityevent for transports (#2417)

* delegate and callbacks instead of unityevent

* using callbacks in Mirror

* Using new callbacks instead of events
* moving methods next to each other in NetworkClient
* moving add/remove to methods in NetworkServer

* replacing uses of events within transports and tests

* fixing tests

* fixing more tests

* replacing delegates with actions

* adding comments to show what action variable's are

* removing extra function created in rebase

* renaming callbacks

* adding reset methods so that actions arn't null

* fixing rename

* breaking defines

* Update Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs

* Update KcpTransport.cs

* Update Transport.cs

* removing ResetHandlers methods

transports can now call events after stop is called

Co-authored-by: vis2k <info@noobtuts.com>
This commit is contained in:
James Frowen 2020-11-20 15:47:56 +00:00 committed by GitHub
parent c3a06b297a
commit d3d3113f7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 149 additions and 156 deletions

View File

@ -36,7 +36,8 @@ public static void AddDefineSymbols()
"MIRROR_24_0_OR_NEWER",
"MIRROR_26_0_OR_NEWER",
"MIRROR_27_0_OR_NEWER",
"MIRROR_28_0_OR_NEWER"
"MIRROR_28_0_OR_NEWER",
"MIRROR_29_0_OR_NEWER"
};
// only touch PlayerSettings if we actually modified it.

View File

@ -147,19 +147,10 @@ public static void DisconnectLocalServer()
static void AddTransportHandlers()
{
Transport.activeTransport.OnClientConnected.AddListener(OnConnected);
Transport.activeTransport.OnClientDataReceived.AddListener(OnDataReceived);
Transport.activeTransport.OnClientDisconnected.AddListener(OnDisconnected);
Transport.activeTransport.OnClientError.AddListener(OnError);
}
static void RemoveTransportHandlers()
{
// so that we don't register them more than once
Transport.activeTransport.OnClientConnected.RemoveListener(OnConnected);
Transport.activeTransport.OnClientDataReceived.RemoveListener(OnDataReceived);
Transport.activeTransport.OnClientDisconnected.RemoveListener(OnDisconnected);
Transport.activeTransport.OnClientError.RemoveListener(OnError);
Transport.activeTransport.OnClientConnected = OnConnected;
Transport.activeTransport.OnClientDataReceived = OnDataReceived;
Transport.activeTransport.OnClientDisconnected = OnDisconnected;
Transport.activeTransport.OnClientError = OnError;
}
static void OnError(Exception exception)
@ -225,7 +216,6 @@ public static void Disconnect()
{
connection.Disconnect();
connection = null;
RemoveTransportHandlers();
}
}
}

View File

@ -90,8 +90,6 @@ public static void Shutdown()
Transport.activeTransport.ServerStop();
}
RemoveTransportHandlers();
initialized = false;
}
dontListen = false;
@ -491,18 +489,10 @@ static void CheckForInactiveConnections()
static void AddTransportHandlers()
{
Transport.activeTransport.OnServerDisconnected.AddListener(OnDisconnected);
Transport.activeTransport.OnServerConnected.AddListener(OnConnected);
Transport.activeTransport.OnServerDataReceived.AddListener(OnDataReceived);
Transport.activeTransport.OnServerError.AddListener(OnError);
}
static void RemoveTransportHandlers()
{
Transport.activeTransport.OnServerDisconnected.RemoveListener(OnDisconnected);
Transport.activeTransport.OnServerConnected.RemoveListener(OnConnected);
Transport.activeTransport.OnServerDataReceived.RemoveListener(OnDataReceived);
Transport.activeTransport.OnServerError.RemoveListener(OnError);
Transport.activeTransport.OnServerConnected = OnConnected;
Transport.activeTransport.OnServerDataReceived = OnDataReceived;
Transport.activeTransport.OnServerDisconnected = OnDisconnected;
Transport.activeTransport.OnServerError = OnError;
}
static void OnConnected(int connectionId)

View File

@ -20,8 +20,6 @@ public void Awake()
{
throw new Exception("FallbackTransport requires at least 1 underlying transport");
}
InitClient();
InitServer();
available = GetAvailableTransport();
Debug.Log("FallbackTransport available: " + available.GetType());
}
@ -54,21 +52,12 @@ public override bool Available()
return available.Available();
}
// clients always pick the first transport
void InitClient()
{
// wire all the base transports to our events
foreach (Transport transport in transports)
{
transport.OnClientConnected.AddListener(OnClientConnected.Invoke);
transport.OnClientDataReceived.AddListener(OnClientDataReceived.Invoke);
transport.OnClientError.AddListener(OnClientError.Invoke);
transport.OnClientDisconnected.AddListener(OnClientDisconnected.Invoke);
}
}
public override void ClientConnect(string address)
{
available.OnClientConnected = OnClientConnected;
available.OnClientDataReceived = OnClientDataReceived;
available.OnClientError = OnClientError;
available.OnClientDisconnected = OnClientDisconnected;
available.ClientConnect(address);
}
@ -107,18 +96,6 @@ public override void ClientSend(int channelId, ArraySegment<byte> segment)
available.ClientSend(channelId, segment);
}
void InitServer()
{
// wire all the base transports to our events
foreach (Transport transport in transports)
{
transport.OnServerConnected.AddListener(OnServerConnected.Invoke);
transport.OnServerDataReceived.AddListener(OnServerDataReceived.Invoke);
transport.OnServerError.AddListener(OnServerError.Invoke);
transport.OnServerDisconnected.AddListener(OnServerDisconnected.Invoke);
}
}
// right now this just returns the first available uri,
// should we return the list of all available uri?
public override Uri ServerUri() => available.ServerUri();
@ -145,6 +122,10 @@ public override void ServerSend(int connectionId, int channelId, ArraySegment<by
public override void ServerStart()
{
available.OnServerConnected = OnServerConnected;
available.OnServerDataReceived = OnServerDataReceived;
available.OnServerError = OnServerError;
available.OnServerDisconnected = OnServerDisconnected;
available.ServerStart();
}

View File

@ -12,26 +12,20 @@ public abstract class MiddlewareTransport : Transport
/// </summary>
public Transport inner;
public virtual void Awake()
{
// listen for inner events and invoke when they are called
inner.OnClientConnected.AddListener(OnClientConnected.Invoke);
inner.OnClientDataReceived.AddListener(OnClientDataReceived.Invoke);
inner.OnClientDisconnected.AddListener(OnClientDisconnected.Invoke);
inner.OnClientError.AddListener(OnClientError.Invoke);
inner.OnServerConnected.AddListener(OnServerConnected.Invoke);
inner.OnServerDataReceived.AddListener(OnServerDataReceived.Invoke);
inner.OnServerDisconnected.AddListener(OnServerDisconnected.Invoke);
inner.OnServerError.AddListener(OnServerError.Invoke);
}
public override bool Available() => inner.Available();
public override int GetMaxPacketSize(int channelId = 0) => inner.GetMaxPacketSize(channelId);
public override void Shutdown() => inner.Shutdown();
#region Client
public override void ClientConnect(string address) => inner.ClientConnect(address);
public override void ClientConnect(string address)
{
inner.OnClientConnected = OnClientConnected;
inner.OnClientDataReceived = OnClientDataReceived;
inner.OnClientDisconnected = OnClientDisconnected;
inner.OnClientError = OnClientError;
inner.ClientConnect(address);
}
public override bool ClientConnected() => inner.ClientConnected();
public override void ClientDisconnect() => inner.ClientDisconnect();
public override void ClientSend(int channelId, ArraySegment<byte> segment) => inner.ClientSend(channelId, segment);
@ -39,7 +33,15 @@ public virtual void Awake()
#region Server
public override bool ServerActive() => inner.ServerActive();
public override void ServerStart() => inner.ServerStart();
public override void ServerStart()
{
inner.OnServerConnected = OnServerConnected;
inner.OnServerDataReceived = OnServerDataReceived;
inner.OnServerDisconnected = OnServerDisconnected;
inner.OnServerError = OnServerError;
inner.ServerStart();
}
public override void ServerStop() => inner.ServerStop();
public override void ServerSend(int connectionId, int channelId, ArraySegment<byte> segment) => inner.ServerSend(connectionId, channelId, segment);
public override bool ServerDisconnect(int connectionId) => inner.ServerDisconnect(connectionId);

View File

@ -17,8 +17,6 @@ public void Awake()
{
Debug.LogError("Multiplex transport requires at least 1 underlying transport");
}
InitClient();
InitServer();
}
void OnEnable()
@ -51,18 +49,6 @@ public override bool Available()
}
#region Client
// clients always pick the first transport
void InitClient()
{
// wire all the base transports to my events
foreach (Transport transport in transports)
{
transport.OnClientConnected.AddListener(OnClientConnected.Invoke);
transport.OnClientDataReceived.AddListener(OnClientDataReceived.Invoke);
transport.OnClientError.AddListener(OnClientError.Invoke);
transport.OnClientDisconnected.AddListener(OnClientDisconnected.Invoke);
}
}
public override void ClientConnect(string address)
{
@ -71,6 +57,10 @@ public override void ClientConnect(string address)
if (transport.Available())
{
available = transport;
transport.OnClientConnected = OnClientConnected;
transport.OnClientDataReceived = OnClientDataReceived;
transport.OnClientError = OnClientError;
transport.OnClientDisconnected = OnClientDisconnected;
transport.ClientConnect(address);
return;
}
@ -86,8 +76,12 @@ public override void ClientConnect(Uri uri)
{
try
{
transport.ClientConnect(uri);
available = transport;
transport.OnClientConnected = OnClientConnected;
transport.OnClientDataReceived = OnClientDataReceived;
transport.OnClientError = OnClientError;
transport.OnClientDisconnected = OnClientDisconnected;
transport.ClientConnect(uri);
return;
}
catch (ArgumentException)
@ -138,7 +132,7 @@ int ToTransportId(int connectionId)
return connectionId % transports.Length;
}
void InitServer()
void AddServerCallbacks()
{
// wire all the base transports to my events
for (int i = 0; i < transports.Length; i++)
@ -148,24 +142,24 @@ void InitServer()
int locali = i;
Transport transport = transports[i];
transport.OnServerConnected.AddListener(baseConnectionId =>
transport.OnServerConnected = (baseConnectionId =>
{
OnServerConnected.Invoke(FromBaseId(locali, baseConnectionId));
});
transport.OnServerDataReceived.AddListener((baseConnectionId, data, channel) =>
transport.OnServerDataReceived = (baseConnectionId, data, channel) =>
{
OnServerDataReceived.Invoke(FromBaseId(locali, baseConnectionId), data, channel);
});
};
transport.OnServerError.AddListener((baseConnectionId, error) =>
transport.OnServerError = (baseConnectionId, error) =>
{
OnServerError.Invoke(FromBaseId(locali, baseConnectionId), error);
});
transport.OnServerDisconnected.AddListener(baseConnectionId =>
};
transport.OnServerDisconnected = baseConnectionId =>
{
OnServerDisconnected.Invoke(FromBaseId(locali, baseConnectionId));
});
};
}
}
@ -222,6 +216,7 @@ public override void ServerStart()
{
foreach (Transport transport in transports)
{
AddServerCallbacks();
transport.ServerStart();
}
}

View File

@ -1,16 +1,8 @@
using System;
using UnityEngine;
using UnityEngine.Events;
namespace Mirror
{
// UnityEvent definitions
[Serializable] public class ClientDataReceivedEvent : UnityEvent<ArraySegment<byte>, int> { }
[Serializable] public class UnityEventException : UnityEvent<Exception> { }
[Serializable] public class UnityEventInt : UnityEvent<int> { }
[Serializable] public class ServerDataReceivedEvent : UnityEvent<int, ArraySegment<byte>, int> { }
[Serializable] public class UnityEventIntException : UnityEvent<int, Exception> { }
/// <summary>
/// Abstract transport layer component
/// </summary>
@ -36,24 +28,27 @@ public abstract class Transport : MonoBehaviour
#region Client
/// <summary>
/// Notify subscribers when when this client establish a successful connection to the server
/// <para>callback()</para>
/// </summary>
[HideInInspector] public UnityEvent OnClientConnected = new UnityEvent();
public Action OnClientConnected = () => Debug.LogWarning("OnClientConnected called with no handler");
/// <summary>
/// Notify subscribers when this client receive data from the server
/// <para>callback(ArraySegment&lt;byte&gt; data, int channel)</para>
/// </summary>
// Note: we provide channelId for NetworkDiagnostics.
[HideInInspector] public ClientDataReceivedEvent OnClientDataReceived = new ClientDataReceivedEvent();
public Action<ArraySegment<byte>, int> OnClientDataReceived = (data, channel) => Debug.LogWarning("OnClientDataReceived called with no handler");
/// <summary>
/// Notify subscribers when this client encounters an error communicating with the server
/// <para>callback(Exception e)</para>
/// </summary>
[HideInInspector] public UnityEventException OnClientError = new UnityEventException();
public Action<Exception> OnClientError = (error) => Debug.LogWarning("OnClientError called with no handler");
/// <summary>
/// Notify subscribers when this client disconnects from the server
/// <para>callback()</para>
/// </summary>
[HideInInspector] public UnityEvent OnClientDisconnected = new UnityEvent();
public Action OnClientDisconnected = () => Debug.LogWarning("OnClientDisconnected called with no handler");
/// <summary>
/// Determines if we are currently connected to the server
@ -106,24 +101,27 @@ public virtual void ClientConnect(Uri uri)
/// <summary>
/// Notify subscribers when a client connects to this server
/// <para>callback(int connId)</para>
/// </summary>
[HideInInspector] public UnityEventInt OnServerConnected = new UnityEventInt();
public Action<int> OnServerConnected = (connId) => Debug.LogWarning("OnServerConnected called with no handler");
/// <summary>
/// Notify subscribers when this server receives data from the client
/// <para>callback(int connId, ArraySegment&lt;byte&gt; data, int channel)</para>
/// </summary>
// Note: we provide channelId for NetworkDiagnostics.
[HideInInspector] public ServerDataReceivedEvent OnServerDataReceived = new ServerDataReceivedEvent();
public Action<int, ArraySegment<byte>, int> OnServerDataReceived = (connId, data, channel) => Debug.LogWarning("OnServerDataReceived called with no handler");
/// <summary>
/// Notify subscribers when this server has some problem communicating with the client
/// <para>callback(int connId, Exception e)</para>
/// </summary>
[HideInInspector] public UnityEventIntException OnServerError = new UnityEventIntException();
public Action<int, Exception> OnServerError = (connId, error) => Debug.LogWarning("OnServerError called with no handler");
/// <summary>
/// Notify subscribers when a client disconnects from this server
/// <para>callback(int connId)</para>
/// </summary>
[HideInInspector] public UnityEventInt OnServerDisconnected = new UnityEventInt();
public Action<int> OnServerDisconnected = (connId) => Debug.LogWarning("OnServerDisconnected called with no handler");
/// <summary>
/// Determines if the server is up and running

View File

@ -1,4 +1,4 @@
// memory transport for easier testing
// memory transport for easier testing
// note: file needs to be outside of Editor folder, otherwise AddComponent
// can't be called with MemoryTransport
using System;

View File

@ -2,7 +2,6 @@
using NSubstitute;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.Events;
namespace Mirror.Tests
{
@ -114,9 +113,12 @@ public void TestClient1Connected()
transport1.Available().Returns(true);
transport2.Available().Returns(true);
UnityAction callback = Substitute.For<UnityAction>();
Action callback = Substitute.For<Action>();
// find available
transport.Awake();
transport.OnClientConnected.AddListener(callback);
// set event and connect to give event to inner
transport.OnClientConnected = callback;
transport.ClientConnect("localhost");
transport1.OnClientConnected.Invoke();
callback.Received().Invoke();
}
@ -124,12 +126,15 @@ public void TestClient1Connected()
[Test]
public void TestClient2Connected()
{
transport1.Available().Returns(true);
transport1.Available().Returns(false);
transport2.Available().Returns(true);
UnityAction callback = Substitute.For<UnityAction>();
Action callback = Substitute.For<Action>();
// find available
transport.Awake();
transport.OnClientConnected.AddListener(callback);
// set event and connect to give event to inner
transport.OnClientConnected = callback;
transport.ClientConnect("localhost");
transport2.OnClientConnected.Invoke();
callback.Received().Invoke();
}
@ -146,15 +151,19 @@ public void TestServerConnected()
transport1.Available().Returns(true);
transport2.Available().Returns(true);
// find available
transport.Awake();
// on connect, send a message back
void SendMessage(int connectionId)
{
transport.ServerSend(connectionId, 5, segment);
}
transport.OnServerConnected.AddListener(SendMessage);
// set event and Start to give event to inner
transport.OnServerConnected = SendMessage;
transport.ServerStart();
transport1.OnServerConnected.Invoke(1);

View File

@ -23,8 +23,6 @@ public void Setup()
middleware = gameObject.AddComponent<MyMiddleware>();
middleware.inner = inner;
//manually call awake in editmode
middleware.Awake();
}
[TearDown]
@ -204,13 +202,15 @@ public void TestServerUri(string address)
}
[Test]
public void TestOnClientConnected()
public void TestClientConnectedCallback()
{
int called = 0;
middleware.OnClientConnected.AddListener(() =>
middleware.OnClientConnected = () =>
{
called++;
});
};
// connect to give callback to inner
middleware.ClientConnect("localhost");
inner.OnClientConnected.Invoke();
Assert.That(called, Is.EqualTo(1));
@ -222,21 +222,22 @@ public void TestOnClientConnected()
[Test]
[TestCase(0)]
[TestCase(1)]
public void TestOnClientDataReceived(int channel)
public void TestClientDataReceivedCallback(int channel)
{
byte[] data = new byte[4];
ArraySegment<byte> segment = new ArraySegment<byte>(data, 1, 2);
int called = 0;
middleware.OnClientDataReceived.AddListener((d, c) =>
middleware.OnClientDataReceived = (d, c) =>
{
called++;
Assert.That(c, Is.EqualTo(channel));
Assert.That(d.Array, Is.EqualTo(segment.Array));
Assert.That(d.Offset, Is.EqualTo(segment.Offset));
Assert.That(d.Count, Is.EqualTo(segment.Count));
});
};
// connect to give callback to inner
middleware.ClientConnect("localhost");
inner.OnClientDataReceived.Invoke(segment, channel);
Assert.That(called, Is.EqualTo(1));
@ -250,13 +251,15 @@ public void TestOnClientDataReceived(int channel)
}
[Test]
public void TestOnClientDisconnected()
public void TestClientDisconnectedCallback()
{
int called = 0;
middleware.OnClientDisconnected.AddListener(() =>
middleware.OnClientDisconnected = () =>
{
called++;
});
};
// connect to give callback to inner
middleware.ClientConnect("localhost");
inner.OnClientDisconnected.Invoke();
Assert.That(called, Is.EqualTo(1));
@ -266,16 +269,18 @@ public void TestOnClientDisconnected()
}
[Test]
public void TestOnClientError()
public void TestClientErrorCallback()
{
Exception exception = new InvalidDataException();
int called = 0;
middleware.OnClientError.AddListener((e) =>
middleware.OnClientError = (e) =>
{
called++;
Assert.That(e, Is.EqualTo(exception));
});
};
// connect to give callback to inner
middleware.ClientConnect("localhost");
inner.OnClientError.Invoke(exception);
Assert.That(called, Is.EqualTo(1));
@ -290,14 +295,16 @@ public void TestOnClientError()
[TestCase(0)]
[TestCase(1)]
[TestCase(19)]
public void TestOnServerConnected(int id)
public void TestServerConnectedCallback(int id)
{
int called = 0;
middleware.OnServerConnected.AddListener((i) =>
middleware.OnServerConnected = (i) =>
{
called++;
Assert.That(i, Is.EqualTo(id));
});
};
// start to give callback to inner
middleware.ServerStart();
inner.OnServerConnected.Invoke(id);
Assert.That(called, Is.EqualTo(1));
@ -313,13 +320,13 @@ public void TestOnServerConnected(int id)
[TestCase(0, 1)]
[TestCase(1, 1)]
[TestCase(19, 1)]
public void TestOnServerDataReceived(int id, int channel)
public void TestServerDataReceivedCallback(int id, int channel)
{
byte[] data = new byte[4];
ArraySegment<byte> segment = new ArraySegment<byte>(data, 1, 2);
int called = 0;
middleware.OnServerDataReceived.AddListener((i, d, c) =>
middleware.OnServerDataReceived = (i, d, c) =>
{
called++;
Assert.That(i, Is.EqualTo(id));
@ -327,8 +334,9 @@ public void TestOnServerDataReceived(int id, int channel)
Assert.That(d.Array, Is.EqualTo(segment.Array));
Assert.That(d.Offset, Is.EqualTo(segment.Offset));
Assert.That(d.Count, Is.EqualTo(segment.Count));
});
};
// start to give callback to inner
middleware.ServerStart();
inner.OnServerDataReceived.Invoke(id, segment, channel);
Assert.That(called, Is.EqualTo(1));
@ -345,14 +353,16 @@ public void TestOnServerDataReceived(int id, int channel)
[TestCase(0)]
[TestCase(1)]
[TestCase(19)]
public void TestOnServerDisconnected(int id)
public void TestServerDisconnectedCallback(int id)
{
int called = 0;
middleware.OnServerDisconnected.AddListener((i) =>
middleware.OnServerDisconnected = (i) =>
{
called++;
Assert.That(i, Is.EqualTo(id));
});
};
// start to give callback to inner
middleware.ServerStart();
inner.OnServerDisconnected.Invoke(id);
Assert.That(called, Is.EqualTo(1));
@ -365,17 +375,19 @@ public void TestOnServerDisconnected(int id)
[TestCase(0)]
[TestCase(1)]
[TestCase(19)]
public void TestOnServerError(int id)
public void TestServerErrorCallback(int id)
{
Exception exception = new InvalidDataException();
int called = 0;
middleware.OnServerError.AddListener((i, e) =>
middleware.OnServerError = (i, e) =>
{
called++;
Assert.That(i, Is.EqualTo(id));
Assert.That(e, Is.EqualTo(exception));
});
};
// start to give callback to inner
middleware.ServerStart();
inner.OnServerError.Invoke(id, exception);
Assert.That(called, Is.EqualTo(1));

View File

@ -2,7 +2,6 @@
using NSubstitute;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.Events;
namespace Mirror.Tests
{
@ -137,8 +136,15 @@ public void TestClientSend()
[Test]
public void TestClient1Connected()
{
UnityAction callback = Substitute.For<UnityAction>();
transport.OnClientConnected.AddListener(callback);
transport1.Available().Returns(true);
transport2.Available().Returns(true);
Action callback = Substitute.For<Action>();
// find available
transport.Awake();
// set event and connect to give event to inner
transport.OnClientConnected = callback;
transport.ClientConnect("localhost");
transport1.OnClientConnected.Invoke();
callback.Received().Invoke();
}
@ -146,8 +152,15 @@ public void TestClient1Connected()
[Test]
public void TestClient2Connected()
{
UnityAction callback = Substitute.For<UnityAction>();
transport.OnClientConnected.AddListener(callback);
transport1.Available().Returns(false);
transport2.Available().Returns(true);
Action callback = Substitute.For<Action>();
// find available
transport.Awake();
// set event and connect to give event to inner
transport.OnClientConnected = callback;
transport.ClientConnect("localhost");
transport2.OnClientConnected.Invoke();
callback.Received().Invoke();
}
@ -169,7 +182,9 @@ void SendMessage(int connectionId)
transport.ServerSend(connectionId, 5, segment);
}
transport.OnServerConnected.AddListener(SendMessage);
// set event and Start to give event to inner
transport.OnServerConnected = SendMessage;
transport.ServerStart();
transport1.OnServerConnected.Invoke(1);

View File

@ -506,7 +506,7 @@ PlayerSettings:
webGLLinkerTarget: 1
webGLThreadsSupport: 0
scriptingDefineSymbols:
1: MIRROR;MIRROR_1726_OR_NEWER;MIRROR_3_0_OR_NEWER;MIRROR_3_12_OR_NEWER;MIRROR_4_0_OR_NEWER;MIRROR_5_0_OR_NEWER;MIRROR_6_0_OR_NEWER;MIRROR_7_0_OR_NEWER;MIRROR_8_0_OR_NEWER;MIRROR_9_0_OR_NEWER;MIRROR_10_0_OR_NEWER;MIRROR_11_0_OR_NEWER;MIRROR_12_0_OR_NEWER;MIRROR_13_0_OR_NEWER;MIRROR_14_0_OR_NEWER;MIRROR_15_0_OR_NEWER;MIRROR_16_0_OR_NEWER;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER
1: MIRROR;MIRROR_1726_OR_NEWER;MIRROR_3_0_OR_NEWER;MIRROR_3_12_OR_NEWER;MIRROR_4_0_OR_NEWER;MIRROR_5_0_OR_NEWER;MIRROR_6_0_OR_NEWER;MIRROR_7_0_OR_NEWER;MIRROR_8_0_OR_NEWER;MIRROR_9_0_OR_NEWER;MIRROR_10_0_OR_NEWER;MIRROR_11_0_OR_NEWER;MIRROR_12_0_OR_NEWER;MIRROR_13_0_OR_NEWER;MIRROR_14_0_OR_NEWER;MIRROR_15_0_OR_NEWER;MIRROR_16_0_OR_NEWER;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER
platformArchitecture: {}
scriptingBackend: {}
il2cppCompilerConfiguration: {}