-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEpicNet.cs
More file actions
441 lines (374 loc) · 18.3 KB
/
Copy pathEpicNet.cs
File metadata and controls
441 lines (374 loc) · 18.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
using System;
using System.Collections.Concurrent;
using System.Threading;
using Epic.OnlineServices;
using Epic.OnlineServices.P2P;
using FishNet.Managing;
using FishNet.Utility.Performance;
using UnityEngine;
namespace FishNet.Transporting.EpicNetPlugin
{
[AddComponentMenu("FishNet/Transport/EpicNet")]
public sealed class EpicNet : Transport
{
[Header("Connection")]
[Tooltip("Maximum number of simultaneously connected players.")]
[Range(1, 65535)]
[SerializeField] int _maximumClients = 4095;
[Tooltip("Shared identifier for the P2P socket. Must be identical on server and all clients.")]
[SerializeField] string socketName = "EpicNet";
[Tooltip("Server's EOS ProductUserId. Required for clients to connect.")]
[SerializeField] string remoteServerProductUserId;
[Space(5)]
[Header("Authentication")]
[Tooltip("Enable automatic EOS login when the transport starts.")]
[SerializeField] bool autoAuthenticate = true;
[Tooltip("Credentials, token, timeout, and account creation options.")]
[SerializeField] AuthData authConnectData = new AuthData();
[Space(5)]
[Header("Security")]
[Tooltip("Maximum new connections allowed per second.")]
[Range(1, 1000)]
[SerializeField] int maxConnectionsPerSecond = 50;
[Tooltip("Additional connections allowed above the per-second limit during bursts.")]
[Range(0, 100)]
[SerializeField] int maxBurstConnections = 10;
[Tooltip("Maximum unconfirmed connections before new requests are rejected.")]
[Range(1, 10000)]
[SerializeField] int maxPendingConnections = 256;
[Tooltip("Time in seconds before a pending connection is dropped if not established.")]
[Range(1f, 120f)]
[SerializeField] float pendingConnectionTimeout = 10f;
[Space(5)]
[Header("Reliability")]
[Tooltip("Maximum number of reliable packets waiting for resend.")]
[Range(1, 100000)]
[SerializeField] int maxRetryQueueSize = 1024;
[Tooltip("Maximum retry attempts per packet before forcing disconnection.")]
[Range(1, 1000)]
[SerializeField] int maxRetryFrames = 120;
[Tooltip("How many retry packets are processed per frame.")]
[Range(1, 500)]
[SerializeField] int maxRetryProcessPerFrame = 32;
[Space(5)]
[Header("Lobbies")]
[Tooltip("Enable EOS lobby creation. The server will advertise itself publicly.")]
[SerializeField] bool enableLobbies = false;
[Tooltip("Name of the lobby. Used if lobbies are enabled.")]
[SerializeField] string lobbyName = "EpicNetGame";
[Space(5)]
[Header("Auto-Reconnect")]
[Tooltip("Enable automatic reconnection on connection interruptions.")]
[SerializeField] bool autoReconnect = true;
[Tooltip("Maximum reconnection attempts before giving up.")]
[Range(1, 100)]
[SerializeField] int maxReconnectAttempts = 5;
[Tooltip("Base delay in seconds before the first reconnection attempt.")]
[Range(0.1f, 10f)]
[SerializeField] float reconnectDelayBase = 1f;
[Tooltip("Maximum delay in seconds between reconnection attempts.")]
[Range(1f, 120f)]
[SerializeField] float reconnectDelayMax = 30f;
[Space(5)]
[Header("Keep-Alive")]
[Tooltip("Enable keep-alive checks. Idle clients will be disconnected after timeout.")]
[SerializeField] bool enableKeepAlive = false;
[Tooltip("Interval in seconds between keep-alive checks.")]
[Range(0.1f, 60f)]
[SerializeField] float keepAliveInterval = 2f;
[Tooltip("Time in seconds without any packet before a client is considered timed out.")]
[Range(1f, 300f)]
[SerializeField] float keepAliveTimeout = 10f;
[Space(5)]
[Header("Relay")]
[Tooltip("Relay policy: NoRelays = direct only, AllowRelays = try direct then relay, ForceRelays = always relay.")]
[SerializeField] RelayPolicy relayPolicy = RelayPolicy.AllowRelays;
public enum RelayPolicy { NoRelays, AllowRelays, ForceRelays }
public RelayPolicy RelayPolicyValue => relayPolicy;
[Space(5)]
[Header("Performance")]
[Tooltip("Maximum number of incoming packets processed per frame. Increase for high-load servers.")]
[Range(1, 10000)]
[SerializeField] int maxIncomingPacketsPerFrame = 100;
[Tooltip("Effective MTU becomes P2P interface max size minus this safety margin.")]
[Range(0, 500)]
[SerializeField] int mtuSafetyMargin = 20;
[Tooltip("Enable lock-free queueing for FishNet's multithreaded transport.")]
[SerializeField] bool enableThreadedMode;
[Space(5)]
[Header("LAN Discovery")]
[Tooltip("Enable LAN server discovery via UDP broadcast.")]
[SerializeField] bool enableLanDiscovery = false;
[Space(10)]
[Header("Debug")]
[Tooltip("Controls the verbosity of log messages.")]
[SerializeField] EpicNetDebugLevel debugLevel = EpicNetDebugLevel.Errors;
[Tooltip("Display real-time network statistics in the Inspector during Play Mode.")]
[SerializeField] bool showStats;
readonly ServerPeer _server = new ServerPeer();
readonly ClientPeer _client = new ClientPeer();
readonly ClientHostPeer _clientHost = new ClientHostPeer();
readonly ClientHostBridge _clientHostBridge = new ClientHostBridge();
internal const int CLIENT_HOST_ID = short.MaxValue;
ConcurrentQueue<ThreadedPacket> _threadedServerOut;
ConcurrentQueue<ThreadedPacket> _threadedClientOut;
ConcurrentQueue<ThreadedPacket> _threadedServerIn;
ConcurrentQueue<ThreadedPacket> _threadedClientIn;
public EpicNetStatistics Stats = new EpicNetStatistics();
public bool AutoAuthenticate => autoAuthenticate;
public AuthData AuthConnectData => authConnectData;
public string SocketName { get => socketName; set => socketName = value; }
public bool IsThreadedMode => enableThreadedMode;
public EpicNetDebugLevel DebugLevel => debugLevel;
public bool EnableLobbies => enableLobbies;
public string RemoteProductUserId
{
get => remoteServerProductUserId;
set => remoteServerProductUserId = value;
}
public string LocalProductUserId =>
EOS.GetPlatformInterface()?.GetConnectInterface()?.GetLoggedInUserByIndex(0)?.ToString() ?? "";
public override void Initialize(NetworkManager networkManager, int transportIndex)
{
base.Initialize(networkManager, transportIndex);
_clientHost.Bind(_clientHostBridge);
_server.SetClientHostBridge(_clientHostBridge);
_client.Initialize(this);
_clientHost.Initialize(this);
_server.Initialize(this);
_server.SetMaximumClients(_maximumClients);
_server.SetSecurityLimits(maxConnectionsPerSecond, maxPendingConnections, pendingConnectionTimeout, maxBurstConnections);
_server.SetRetrySettings(maxRetryQueueSize, maxRetryFrames, maxRetryProcessPerFrame, maxIncomingPacketsPerFrame);
_server.SetLobbySettings(enableLobbies, lobbyName);
_server.SetKeepAlive(enableKeepAlive, keepAliveInterval, keepAliveTimeout);
_server.SetLanDiscovery(enableLanDiscovery);
_client.SetRetrySettings(maxRetryQueueSize, maxRetryFrames, maxRetryProcessPerFrame, maxIncomingPacketsPerFrame);
_client.SetReconnectSettings(autoReconnect, maxReconnectAttempts, reconnectDelayBase, reconnectDelayMax);
if (enableThreadedMode)
{
_threadedServerOut = new ConcurrentQueue<ThreadedPacket>();
_threadedClientOut = new ConcurrentQueue<ThreadedPacket>();
_threadedServerIn = new ConcurrentQueue<ThreadedPacket>();
_threadedClientIn = new ConcurrentQueue<ThreadedPacket>();
}
}
void OnDestroy() => Shutdown();
void Update()
{
if (!EOS.IsReady()) { Shutdown(); return; }
_clientHost.PollServerReady();
_client.CheckDeferredStop();
if (enableThreadedMode)
{
ProcessThreadedSending();
ProcessThreadedReceiving();
}
if (_server.GetLocalConnectionState() == LocalConnectionState.Started)
{
_server.ProcessRetryQueue();
_server.CleanupPendingConnections();
_clientHostBridge.ProcessServerIncoming();
}
if (_clientHost.GetLocalConnectionState() == LocalConnectionState.Started)
{
_clientHostBridge.ProcessClientHostIncoming();
_clientHost.IterateIncoming();
}
if (showStats) Stats.Calculate();
}
void ProcessThreadedSending()
{
while (_threadedServerOut.TryDequeue(out var pkt))
{
_server.SendToClient(pkt.ChannelId, new ArraySegment<byte>(pkt.Data, 0, pkt.Length), pkt.ConnectionId, pkt.Priority);
Interlocked.Increment(ref Stats.PacketsSent);
Interlocked.Add(ref Stats.BytesSent, pkt.Length);
pkt.ReturnToPool();
}
while (_threadedClientOut.TryDequeue(out var pkt))
{
var seg = new ArraySegment<byte>(pkt.Data, 0, pkt.Length);
if (_clientHost.GetLocalConnectionState() == LocalConnectionState.Started)
_clientHost.SendToServer(pkt.ChannelId, seg, pkt.Priority);
else
_client.SendToServer(pkt.ChannelId, seg, pkt.Priority);
Interlocked.Increment(ref Stats.PacketsSent);
Interlocked.Add(ref Stats.BytesSent, pkt.Length);
pkt.ReturnToPool();
}
}
void ProcessThreadedReceiving()
{
if (_server.GetLocalConnectionState() == LocalConnectionState.Started)
_server.ReceiveToQueue(_threadedServerIn);
if (_client.GetLocalConnectionState() == LocalConnectionState.Started)
_client.ReceiveToQueue(_threadedClientIn);
}
public override string GetConnectionAddress(int connectionId) =>
connectionId == CLIENT_HOST_ID ? LocalProductUserId : _server.GetConnectionAddress(connectionId);
public override event Action<ClientConnectionStateArgs> OnClientConnectionState;
public override event Action<ServerConnectionStateArgs> OnServerConnectionState;
public override event Action<RemoteConnectionStateArgs> OnRemoteConnectionState;
public override LocalConnectionState GetConnectionState(bool server) =>
server ? _server.GetLocalConnectionState() : _client.GetLocalConnectionState();
public override RemoteConnectionState GetConnectionState(int connectionId) =>
_server.GetConnectionState(connectionId);
public override void HandleClientConnectionState(ClientConnectionStateArgs a) =>
OnClientConnectionState?.Invoke(a);
public override void HandleServerConnectionState(ServerConnectionStateArgs a) =>
OnServerConnectionState?.Invoke(a);
public override void HandleRemoteConnectionState(RemoteConnectionStateArgs a) =>
OnRemoteConnectionState?.Invoke(a);
public override void IterateIncoming(bool server)
{
if (enableThreadedMode)
{
if (server)
DrainIncomingQueue(_threadedServerIn, true);
else
DrainIncomingQueue(_threadedClientIn, false);
}
else
{
if (server)
_server.IterateIncoming();
else
{
_client.IterateIncoming();
_clientHost.IterateIncoming();
}
}
}
void DrainIncomingQueue(ConcurrentQueue<ThreadedPacket> queue, bool server)
{
while (queue.TryDequeue(out var pkt))
{
var seg = new ArraySegment<byte>(pkt.Data, 0, pkt.Length);
if (server)
HandleServerReceivedDataArgs(new ServerReceivedDataArgs(seg, (Channel)pkt.ChannelId, pkt.ConnectionId, Index));
else
HandleClientReceivedDataArgs(new ClientReceivedDataArgs(seg, (Channel)pkt.ChannelId, Index));
pkt.ReturnToPool();
}
}
public override void IterateOutgoing(bool server)
{
if (enableThreadedMode) return;
if (server) _server.IterateOutgoing();
else _client.IterateOutgoing();
}
public override event Action<ClientReceivedDataArgs> OnClientReceivedData;
public override event Action<ServerReceivedDataArgs> OnServerReceivedData;
public override void HandleClientReceivedDataArgs(ClientReceivedDataArgs a) =>
OnClientReceivedData?.Invoke(a);
public override void HandleServerReceivedDataArgs(ServerReceivedDataArgs a) =>
OnServerReceivedData?.Invoke(a);
public override void SendToServer(byte channelId, ArraySegment<byte> segment)
{
if (enableThreadedMode)
_threadedClientOut?.Enqueue(new ThreadedPacket(channelId, segment));
else
{
if (_clientHost.GetLocalConnectionState() == LocalConnectionState.Started)
_clientHost.SendToServer(channelId, segment);
else
_client.SendToServer(channelId, segment);
Interlocked.Increment(ref Stats.PacketsSent);
Interlocked.Add(ref Stats.BytesSent, segment.Count);
}
}
public override void SendToClient(byte channelId, ArraySegment<byte> segment, int connectionId)
{
if (enableThreadedMode)
_threadedServerOut?.Enqueue(new ThreadedPacket(channelId, segment, connectionId));
else
{
_server.SendToClient(channelId, segment, connectionId);
Interlocked.Increment(ref Stats.PacketsSent);
Interlocked.Add(ref Stats.BytesSent, segment.Count);
}
}
public override bool IsLocalTransport(int connectionId) => connectionId == CLIENT_HOST_ID;
public override int GetMaximumClients() => _maximumClients;
public override void SetMaximumClients(int value) { _maximumClients = value; _server.SetMaximumClients(value); }
public override void SetClientAddress(string address) { }
public override void SetServerBindAddress(string address, IPAddressType addressType) { }
public override void SetPort(ushort port) { }
public override bool StartConnection(bool server) => server ? StartServer() : StartClient();
public override bool StopConnection(bool server) => server ? StopServer() : StopClient();
public override bool StopConnection(int connectionId, bool immediately) => _server.StopConnection(connectionId);
public override void Shutdown()
{
StopConnection(false);
StopConnection(true);
DrainAllQueues();
_clientHostBridge.Reset();
Stats.Reset();
}
void DrainAllQueues()
{
if (_threadedServerOut is null) return;
while (_threadedServerOut.TryDequeue(out var p)) p.ReturnToPool();
while (_threadedClientOut.TryDequeue(out var p)) p.ReturnToPool();
while (_threadedServerIn.TryDequeue(out var p)) p.ReturnToPool();
while (_threadedClientIn.TryDequeue(out var p)) p.ReturnToPool();
}
bool StartServer()
{
if (_server.GetLocalConnectionState() != LocalConnectionState.Stopped)
{
NetworkManager.LogError("Server is already running.");
return false;
}
bool clientWasRunning = _client.GetLocalConnectionState() != LocalConnectionState.Stopped;
if (clientWasRunning) _client.StopConnection();
bool result = _server.StartConnection();
if (result && clientWasRunning) StartConnection(false);
return result;
}
bool StopServer() => _server.StopConnection();
bool StartClient()
{
if (_server.GetLocalConnectionState() == LocalConnectionState.Stopped)
{
if (_client.GetLocalConnectionState() != LocalConnectionState.Stopped)
{
NetworkManager.LogError("Client is already running.");
return false;
}
if (string.IsNullOrWhiteSpace(remoteServerProductUserId))
{
NetworkManager.LogError("[EpicNet] RemoteProductUserId is empty.");
return false;
}
var testId = ProductUserId.FromString(remoteServerProductUserId);
if (testId == null || !testId.IsValid())
{
NetworkManager.LogError($"[EpicNet] Invalid RemoteProductUserId: '{remoteServerProductUserId}'");
return false;
}
if (_clientHost.GetLocalConnectionState() != LocalConnectionState.Stopped)
_clientHost.StopConnection();
_client.StartConnection();
}
else
{
_clientHost.StartConnection(_server);
}
return true;
}
bool StopClient()
{
bool r = _client.StopConnection();
r |= _clientHost.StopConnection();
return r;
}
public override int GetMTU(byte channel) => P2PInterface.MAX_PACKET_SIZE - mtuSafetyMargin;
internal void LogDebug(string msg)
{ if (debugLevel >= EpicNetDebugLevel.Verbose) Debug.Log(msg); }
internal void LogWarn(string msg)
{ if (debugLevel >= EpicNetDebugLevel.Warnings) Debug.LogWarning(msg); }
internal void LogErr(string msg)
{ if (debugLevel >= EpicNetDebugLevel.Errors) Debug.LogError(msg); }
}
}