Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
MultiPlayerEnvironment.cs
1//#define LineListener
2
3using System;
5using System.Threading;
6using System.Threading.Tasks;
7using System.Net;
8using Waher.Events;
12#if LineListener
14#endif
15
17{
21 public enum MultiPlayerState
22 {
26 Created,
27
31 Reinitializing,
32
36 SearchingForGateway,
37
41 RegisteringApplicationInGateway,
42
46 FindingPlayers,
47
51 ConnectingPlayers,
52
56 Ready,
57
61 Error,
62
66 Closed
67 }
68
73 {
74 private PeerToPeerNetwork p2pNetwork;
75 private MqttClient mqttConnection = null;
76 private MultiPlayerState state = MultiPlayerState.Created;
77 private ManualResetEvent ready = new ManualResetEvent(false);
78 private ManualResetEvent error = new ManualResetEvent(false);
79 private Exception exception;
80 private Player[] remotePlayers = Array.Empty<Player>();
81 private int mqttTerminatedPacketIdentifier;
82 private int playerCount = 1;
83 private int connectionCount = 0;
84 private readonly Player localPlayer;
85 private readonly Dictionary<IPEndPoint, Player> remotePlayersByEndpoint = new Dictionary<IPEndPoint, Player>();
86 private readonly Dictionary<IPAddress, bool> remotePlayerIPs = new Dictionary<IPAddress, bool>();
87 private readonly Dictionary<Guid, Player> playersById = new Dictionary<Guid, Player>();
88 private readonly SortedDictionary<int, Player> remotePlayersByIndex = new SortedDictionary<int, Player>();
89 private readonly string applicationName;
90 private readonly string mqttServer;
91 private readonly int mqttPort;
92 private readonly string mqttNegotiationTopic;
93 private readonly string mqttUserName;
94 private readonly string mqttPassword;
95 private readonly bool mqttTls;
96
111 public MultiPlayerEnvironment(string ApplicationName, bool AllowMultipleApplicationsOnSameMachine,
112 string MqttServer, int MqttPort, bool MqttTls, string MqttUserName, string MqttPassword,
113 string MqttNegotiationTopic, int EstimatedMaxNrPlayers, Guid PlayerId, params KeyValuePair<string, string>[] PlayerMetaInfo)
114 {
115 this.localPlayer = new Player(PlayerId, new IPEndPoint(IPAddress.Any, 0), new IPEndPoint(IPAddress.Any, 0), PlayerMetaInfo);
116 this.playersById[PlayerId] = this.localPlayer;
117 this.applicationName = ApplicationName;
118
119 this.mqttServer = MqttServer;
120 this.mqttPort = MqttPort;
121 this.mqttTls = MqttTls;
122 this.mqttUserName = MqttUserName;
123 this.mqttPassword = MqttPassword;
124 this.mqttNegotiationTopic = MqttNegotiationTopic;
125
126 this.p2pNetwork = new PeerToPeerNetwork(AllowMultipleApplicationsOnSameMachine ? this.applicationName + " (" + PlayerId.ToString() + ")" :
127 this.applicationName, 0, 0, EstimatedMaxNrPlayers);
128 this.p2pNetwork.OnStateChange += this.P2PNetworkStateChange;
129 this.p2pNetwork.OnPeerConnected += this.P2pNetwork_OnPeerConnected;
130 this.p2pNetwork.OnUdpDatagramReceived += this.P2pNetwork_OnUdpDatagramReceived;
131 }
132
133 private async Task P2pNetwork_OnUdpDatagramReceived(object Sender, UdpDatagramEventArgs e)
134 {
136
137 lock (this.remotePlayersByEndpoint)
138 {
139 if (!this.remotePlayersByEndpoint.TryGetValue(e.RemoteEndpoint, out Player))
140 return;
141 }
142
143 if (!(Player.Connection is null))
144 await Player.Connection.UdpDatagramReceived(Sender, e);
145 }
146
147 private async Task P2PNetworkStateChange(object Sender, PeerToPeerNetworkState NewState)
148 {
149 switch (NewState)
150 {
151 case PeerToPeerNetworkState.Created:
152 await this.SetState(MultiPlayerState.Created);
153 break;
154
155 case PeerToPeerNetworkState.Reinitializing:
156 await this.SetState(MultiPlayerState.Reinitializing);
157 break;
158
159 case PeerToPeerNetworkState.SearchingForGateway:
160 await this.SetState(MultiPlayerState.SearchingForGateway);
161 break;
162
163 case PeerToPeerNetworkState.RegisteringApplicationInGateway:
164 await this.SetState(MultiPlayerState.RegisteringApplicationInGateway);
165 break;
166
167 case PeerToPeerNetworkState.Ready:
168 try
169 {
170 this.exception = null;
171
172 this.localPlayer.SetEndpoints(this.p2pNetwork.ExternalEndpoint, this.p2pNetwork.LocalEndpoint);
173
174 this.mqttConnection = new MqttClient(this.mqttServer, this.mqttPort, this.mqttTls, this.mqttUserName, this.mqttPassword);
175 this.mqttConnection.OnConnectionError += this.MqttConnection_OnConnectionError;
176 this.mqttConnection.OnError += this.MqttConnection_OnError;
177 this.mqttConnection.OnStateChanged += this.MqttConnection_OnStateChanged;
178 this.mqttConnection.OnContentReceived += this.MqttConnection_OnContentReceived;
179
180 await this.SetState(MultiPlayerState.FindingPlayers);
181 }
182 catch (Exception ex)
183 {
184 this.exception = ex;
185 await this.SetState(MultiPlayerState.Error);
186 }
187 break;
188
189 case PeerToPeerNetworkState.Error:
190 this.exception = this.p2pNetwork.Exception;
191 await this.SetState(MultiPlayerState.Error);
192 break;
193
194 case PeerToPeerNetworkState.Closed:
195 await this.SetState(MultiPlayerState.Closed);
196 break;
197 }
198 }
199
200 private async Task MqttConnection_OnStateChanged(object Sender, MqttState NewState)
201 {
202 if (NewState == MqttState.Connected)
203 {
204 await this.mqttConnection.SUBSCRIBE(this.mqttNegotiationTopic);
205
206 BinaryOutput Output = new BinaryOutput();
207 Output.WriteByte(0);
208 Output.WriteString(this.applicationName);
209
210 this.localPlayer.SetEndpoints(this.p2pNetwork.ExternalEndpoint, this.p2pNetwork.LocalEndpoint);
211 this.Serialize(this.localPlayer, Output);
212
213 await this.mqttConnection.PUBLISH(this.mqttNegotiationTopic, MqttQualityOfService.AtLeastOnce, false, Output);
214
215#if LineListener
216 ConsoleOut.WriteLine("Tx: HELLO(" + this.localPlayer.ToString() + ")");
217#endif
218 }
219 }
220
221 private void Serialize(Player Player, BinaryOutput Output)
222 {
223 Output.WriteString(Player.PublicEndpoint.Address.ToString());
224 Output.WriteUInt16((ushort)Player.PublicEndpoint.Port);
225
226 Output.WriteString(Player.LocalEndpoint.Address.ToString());
227 Output.WriteUInt16((ushort)Player.LocalEndpoint.Port);
228
229 Output.WriteGuid(Player.PlayerId);
230 Output.WriteUInt((uint)Player.Count);
231
232 foreach (KeyValuePair<string, string> P in Player)
233 {
234 Output.WriteString(P.Key);
235 Output.WriteString(P.Value);
236 }
237 }
238
239 private Player Deserialize(BinaryInput Input)
240 {
241 IPAddress PublicAddress = IPAddress.Parse(Input.ReadString());
242 ushort PublicPort = Input.ReadUInt16();
243 IPEndPoint PublicEndpoint = new IPEndPoint(PublicAddress, PublicPort);
244
245 IPAddress LocalAddress = IPAddress.Parse(Input.ReadString());
246 ushort LocalPort = Input.ReadUInt16();
247 IPEndPoint LocalEndpoint = new IPEndPoint(LocalAddress, LocalPort);
248
249 Guid PlayerId = Input.ReadGuid();
250 bool LocalPlayer = PlayerId == this.localPlayer.PlayerId;
251 int i, c = (int)Input.ReadUInt();
252 KeyValuePair<string, string>[] PlayerMetaInfo = LocalPlayer ? null : new KeyValuePair<string, string>[c];
253 string Key, Value;
254
255 for (i = 0; i < c; i++)
256 {
257 Key = Input.ReadString();
258 Value = Input.ReadString();
259 if (!LocalPlayer)
260 PlayerMetaInfo[i] = new KeyValuePair<string, string>(Key, Value);
261 }
262
263 if (LocalPlayer)
264 return null;
265 else
266 return new Player(PlayerId, PublicEndpoint, LocalEndpoint, PlayerMetaInfo);
267 }
268
269 private async Task MqttConnection_OnContentReceived(object Sender, MqttContent Content)
270 {
271 BinaryInput Input = Content.DataInput;
272 byte Command = Input.ReadByte();
273
274 switch (Command)
275 {
276 case 0: // Hello
277 string ApplicationName = Input.ReadString();
278 if (ApplicationName != this.applicationName)
279 break;
280
281 Player Player = this.Deserialize(Input);
282 if (Player is null)
283 break;
284
285#if LineListener
286 ConsoleOut.WriteLine("Rx: HELLO(" + Player.ToString() + ")");
287#endif
288 IPEndPoint ExpectedEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
289
290 lock (this.remotePlayersByEndpoint)
291 {
292 this.remotePlayersByEndpoint[ExpectedEndpoint] = Player;
293 this.remotePlayerIPs[ExpectedEndpoint.Address] = true;
294 this.playersById[Player.PlayerId] = Player;
295
296 this.UpdateRemotePlayersLocked();
297 }
298
299 await this.OnPlayerAvailable.Raise(this, Player);
300 break;
301
302 case 1: // Interconnect
303 ApplicationName = Input.ReadString();
304 if (ApplicationName != this.applicationName)
305 break;
306
307 Player = this.Deserialize(Input);
308 if (Player is null)
309 break;
310
311#if LineListener
312 ConsoleOut.Write("Rx: INTERCONNECT(" + Player.ToString());
313#endif
314 int Index = 0;
315 int i, c;
316 LinkedList<Player> Players = new LinkedList<Player>();
317 bool LocalPlayerIncluded = false;
318
319 Player.Index = Index++;
320 Players.AddLast(Player);
321
322 c = (int)Input.ReadUInt();
323 for (i = 0; i < c; i++)
324 {
325 Player = this.Deserialize(Input);
326 if (Player is null)
327 {
328#if LineListener
329 ConsoleOut.Write("," + this.localPlayer.ToString());
330#endif
331 this.localPlayer.Index = Index++;
332 LocalPlayerIncluded = true;
333 }
334 else
335 {
336#if LineListener
337 ConsoleOut.Write("," + Player.ToString());
338#endif
339 Player.Index = Index++;
340 Players.AddLast(Player);
341 }
342 }
343
344#if LineListener
346#endif
347 if (!LocalPlayerIncluded)
348 break;
349
350 await this.mqttConnection.DisposeAsync();
351 this.mqttConnection = null;
352
353 lock (this.remotePlayersByEndpoint)
354 {
355 this.remotePlayersByEndpoint.Clear();
356 this.remotePlayerIPs.Clear();
357 this.remotePlayersByIndex.Clear();
358 this.playersById.Clear();
359
360 this.remotePlayersByIndex[this.localPlayer.Index] = this.localPlayer;
361 this.playersById[this.localPlayer.PlayerId] = this.localPlayer;
362
363 foreach (Player Player2 in Players)
364 {
365 ExpectedEndpoint = Player2.GetExpectedEndpoint(this.p2pNetwork);
366
367 this.remotePlayersByIndex[Player2.Index] = Player2;
368 this.remotePlayersByEndpoint[ExpectedEndpoint] = Player2;
369 this.remotePlayerIPs[ExpectedEndpoint.Address] = true;
370 this.playersById[Player2.PlayerId] = Player2;
371 }
372
373 this.UpdateRemotePlayersLocked();
374 }
375
376 await this.SetState(MultiPlayerState.ConnectingPlayers);
377 await this.StartConnecting();
378 break;
379
380 case 2: // Bye
381 ApplicationName = Input.ReadString();
382 if (ApplicationName != this.applicationName)
383 break;
384
385 Guid PlayerId = Input.ReadGuid();
386 lock (this.remotePlayersByEndpoint)
387 {
388 if (!this.playersById.TryGetValue(PlayerId, out Player))
389 break;
390
391#if LineListener
392 ConsoleOut.WriteLine("Rx: BYE(" + Player.ToString() + ")");
393#endif
394 ExpectedEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
395
396 this.playersById.Remove(PlayerId);
397 this.remotePlayersByEndpoint.Remove(ExpectedEndpoint);
398 this.remotePlayersByIndex.Remove(Player.Index);
399
400 IPAddress ExpectedAddress = ExpectedEndpoint.Address;
401 bool AddressFound = false;
402
403 foreach (IPEndPoint EP in this.remotePlayersByEndpoint.Keys)
404 {
405 if (IPAddress.Equals(EP.Address, ExpectedAddress))
406 {
407 AddressFound = true;
408 break;
409 }
410 }
411
412 if (!AddressFound)
413 this.remotePlayerIPs.Remove(ExpectedAddress);
414
415 this.UpdateRemotePlayersLocked();
416 }
417 break;
418 }
419 }
420
421 private void UpdateRemotePlayersLocked()
422 {
423 int c = this.remotePlayersByEndpoint.Count;
424
425 this.playerCount = 1 + c;
426 this.remotePlayers = new Player[c];
427 this.remotePlayersByEndpoint.Values.CopyTo(this.remotePlayers, 0);
428 }
429
430 private async Task P2pNetwork_OnPeerConnected(object Listener, PeerConnection Peer)
431 {
432 IPEndPoint Endpoint = (IPEndPoint)Peer.Tcp.Client.Client.RemoteEndPoint;
433
434#if LineListener
435 ConsoleOut.WriteLine("Receiving connection from " + Endpoint.ToString());
436#endif
437
438 bool Dispose = false;
439
440 lock (this.remotePlayersByEndpoint)
441 {
442 if (!this.remotePlayerIPs.ContainsKey(Endpoint.Address))
443 Dispose = true;
444 }
445
446 if (Dispose)
447 {
448 await Peer.DisposeAsync();
449 return;
450 }
451
452 Peer.OnClosed += this.Peer_OnClosed;
453 Peer.OnReceived += this.Peer_OnReceived;
454
455 BinaryOutput Output = new BinaryOutput();
456
457 Output.WriteGuid(this.localPlayer.PlayerId);
458 Output.WriteString(this.ExternalEndpoint.Address.ToString());
459 Output.WriteUInt16((ushort)this.ExternalEndpoint.Port);
460
461 await Peer.SendTcp(true, Output.GetPacket());
462 }
463
464 private async Task<bool> Peer_OnReceived(object Sender, bool ConstantBuffer, byte[] Buffer, int Offset, int Count)
465 {
466 PeerConnection Connection = (PeerConnection)Sender;
467 Player Player;
468 byte[] Packet;
469
470 if (Connection.StateObject is null)
471 {
472 BinaryInput Input = new BinaryInput(Buffer, Offset, Count);
473 Guid PlayerId;
474 IPAddress PlayerRemoteAddress;
475 IPEndPoint PlayerRemoteEndpoint;
476
477 try
478 {
479 PlayerId = Input.ReadGuid();
480 PlayerRemoteAddress = IPAddress.Parse(Input.ReadString());
481 PlayerRemoteEndpoint = new IPEndPoint(PlayerRemoteAddress, Input.ReadUInt16());
482 }
483 catch (Exception)
484 {
485 if (!(Connection is null))
486 await Connection.DisposeAsync();
487
488 return true;
489 }
490
491 if (Input.BytesLeft == 0)
492 Packet = null;
493 else
494 Packet = Input.GetRemainingData();
495
496 bool AllConnected = false;
497 bool DisposeConnection = false;
498 PeerConnection ObsoleteConnection = null;
499
500 lock (this.remotePlayersByEndpoint)
501 {
502 if (!this.playersById.TryGetValue(PlayerId, out Player))
503 DisposeConnection = true;
504 else
505 {
506 if (Player.Connection is null)
507 this.connectionCount++;
508 else
509 ObsoleteConnection = Player.Connection;
510
511 Player.Connection = Connection;
512 Connection.StateObject = Player;
513 Connection.RemoteEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
514
515 AllConnected = this.connectionCount + 1 == this.playerCount;
516 }
517 }
518
519 if (DisposeConnection)
520 {
521 if (!(Connection is null))
522 await Connection.DisposeAsync();
523
524 return true;
525 }
526
527 if (!(ObsoleteConnection is null))
528 await ObsoleteConnection.DisposeAsync();
529
530 await this.OnPlayerConnected.Raise(this, Player);
531
532 if (AllConnected)
533 await this.SetState(MultiPlayerState.Ready);
534
535 if (Packet is null)
536 return true;
537 }
538 else
539 {
540 Player = (Player)Connection.StateObject;
541 Packet = SnifferBase.CloneSection(Buffer, Offset, Count);
542 }
543
544 await this.GameDataReceived(Player, Connection, Packet);
545
546 return true;
547 }
548
555 protected virtual Task GameDataReceived(Player FromPlayer, PeerConnection Connection, byte[] Packet)
556 {
557 return this.OnGameDataReceived.Raise(this, new GameDataEventArgs(FromPlayer, Connection, Packet));
558 }
559
563 public event EventHandlerAsync<GameDataEventArgs> OnGameDataReceived = null;
564
570 [Obsolete("Use an overload with a ConstantBuffer argument. This increases performance, as the buffer will not be unnecessarily cloned if queued.")]
571 public Task SendTcpToAll(byte[] Packet)
572 {
573 return this.SendTcpToAll(false, Packet);
574 }
575
583 public async Task SendTcpToAll(bool ConstantBuffer, byte[] Packet)
584 {
585 if (this.state != MultiPlayerState.Ready)
586 throw new Exception("The multiplayer environment is not ready to exchange data between players.");
587
588 PeerConnection Connection;
589 foreach (Player Player in this.remotePlayers)
590 {
591 if (!((Connection = Player.Connection) is null))
592 await Connection.SendTcp(ConstantBuffer, Packet);
593 }
594 }
595
602 [Obsolete("Use an overload with a ConstantBuffer argument. This increases performance, as the buffer will not be unnecessarily cloned if queued.")]
603 public Task SendTcpTo(Player Player, byte[] Packet)
604 {
605 return this.SendTcpTo(Player, false, Packet);
606 }
607
616 public Task SendTcpTo(Player Player, bool ConstantBuffer, byte[] Packet)
617 {
618 if (this.state != MultiPlayerState.Ready)
619 throw new Exception("The multiplayer environment is not ready to exchange data between players.");
620
621 PeerConnection Connection = Player.Connection;
622 return Connection?.SendTcp(ConstantBuffer, Packet) ?? Task.CompletedTask;
623 }
624
631 [Obsolete("Use an overload with a ConstantBuffer argument. This increases performance, as the buffer will not be unnecessarily cloned if queued.")]
632 public Task SendTcpTo(Guid PlayerId, byte[] Packet)
633 {
634 return this.SendTcpTo(PlayerId, false, Packet);
635 }
636
645 public Task SendTcpTo(Guid PlayerId, bool ConstantBuffer, byte[] Packet)
646 {
648
649 lock (this.remotePlayersByEndpoint)
650 {
651 if (!this.playersById.TryGetValue(PlayerId, out Player))
652 throw new ArgumentException("No player with that ID.", nameof(PlayerId));
653 }
654
655 PeerConnection Connection = Player.Connection;
656 return Connection?.SendTcp(ConstantBuffer, Packet) ?? Task.CompletedTask;
657 }
658
666 public async Task SendUdpToAll(byte[] Packet, int IncludeNrPreviousPackets)
667 {
668 if (this.state != MultiPlayerState.Ready)
669 throw new Exception("The multiplayer environment is not ready to exchange data between players.");
670
671 PeerConnection Connection;
672 foreach (Player Player in this.remotePlayers)
673 {
674 if (!((Connection = Player.Connection) is null))
675 await Connection.SendUdp(Packet, IncludeNrPreviousPackets);
676 }
677 }
678
687 public Task SendUdpTo(Player Player, byte[] Packet, int IncludeNrPreviousPackets)
688 {
689 if (this.state != MultiPlayerState.Ready)
690 throw new Exception("The multiplayer environment is not ready to exchange data between players.");
691
692 PeerConnection Connection = Player.Connection;
693 return Connection?.SendUdp(Packet, IncludeNrPreviousPackets) ?? Task.CompletedTask;
694 }
695
704 public Task SendUdpTo(Guid PlayerId, byte[] Packet, int IncludeNrPreviousPackets)
705 {
707
708 lock (this.remotePlayersByEndpoint)
709 {
710 if (!this.playersById.TryGetValue(PlayerId, out Player))
711 throw new ArgumentException("No player with that ID.", nameof(PlayerId));
712 }
713
714 PeerConnection Connection = Player.Connection;
715 return Connection?.SendUdp(Packet, IncludeNrPreviousPackets) ?? Task.CompletedTask;
716 }
717
718 private async Task Peer_OnClosed(object Sender, EventArgs e)
719 {
720 PeerConnection Connection = (PeerConnection)Sender;
721 Player Player = (Player)Connection.StateObject;
722 if (Player is null)
723 return;
724
725 if (Player.Connection != Connection)
726 return;
727
728 lock (this.remotePlayersByEndpoint)
729 {
730 Player.Connection = null;
731 this.connectionCount--;
732
733 Connection.StateObject = null;
734 }
735
736 await this.OnPlayerDisconnected.Raise(this, Player);
737 }
738
742 public event EventHandlerAsync<Player> OnPlayerAvailable = null;
743
747 public event EventHandlerAsync<Player> OnPlayerConnected = null;
748
752 public event EventHandlerAsync<Player> OnPlayerDisconnected = null;
753
757 public async Task ConnectPlayers()
758 {
759 if (this.state != MultiPlayerState.FindingPlayers)
760 throw new Exception("The multiplayer environment is not in the state of finding players.");
761
762 await this.SetState(MultiPlayerState.ConnectingPlayers);
763
764 int Index = 0;
765 BinaryOutput Output = new BinaryOutput();
766 Output.WriteByte(1);
767 Output.WriteString(this.applicationName);
768 this.localPlayer.Index = Index++;
769 this.Serialize(this.localPlayer, Output);
770
771#if LineListener
772 ConsoleOut.Write("Tx: INTERCONNECT(" + this.localPlayer.ToString());
773#endif
774 lock (this.remotePlayersByEndpoint)
775 {
776 Output.WriteUInt((uint)this.remotePlayersByEndpoint.Count);
777
778 foreach (Player Player in this.remotePlayersByEndpoint.Values)
779 {
780 Player.Index = Index++;
781 this.Serialize(Player, Output);
782
783#if LineListener
785#endif
786 }
787 }
788
789 this.mqttTerminatedPacketIdentifier = await this.mqttConnection.PUBLISH(this.mqttNegotiationTopic, MqttQualityOfService.AtLeastOnce, false, Output);
790 this.mqttConnection.OnPublished += this.MqttConnection_OnPublished;
791
792#if LineListener
794#endif
795 await this.StartConnecting();
796 }
797
798 private async Task StartConnecting()
799 {
800#if LineListener
801 ConsoleOut.WriteLine("Current player has index " + this.localPlayer.Index.ToString());
802#endif
803 if (this.remotePlayers.Length == 0)
804 await this.SetState(MultiPlayerState.Ready);
805 else
806 {
807 foreach (Player Player in this.remotePlayers)
808 {
809 if (Player.Index < this.localPlayer.Index)
810 {
811#if LineListener
812 ConsoleOut.WriteLine("Connecting to " + Player.ToString() + " (index " + Player.Index.ToString() + ")");
813#endif
814 PeerConnection Connection = await this.p2pNetwork.ConnectToPeer(Player.PublicEndpoint);
815
816 Connection.StateObject = Player;
817 Connection.OnClosed += this.Peer_OnClosed;
818 Connection.OnReceived += this.Connection_OnReceived;
819
820 Connection.Start();
821 }
822 else
823 {
824#if LineListener
825 ConsoleOut.WriteLine("Waiting for connection from " + Player.ToString() + " (index " + Player.Index.ToString() + ")");
826#endif
827 }
828 }
829 }
830 }
831
832 private async Task<bool> Connection_OnReceived(object Sender, bool ConstantBuffer, byte[] Buffer, int Offset, int Count)
833 {
834 PeerConnection Connection = (PeerConnection)Sender;
835 Guid PlayerId;
836 IPAddress PlayerRemoteAddress;
837 IPEndPoint PlayerRemoteEndpoint;
838
839 try
840 {
841 BinaryInput Input = new BinaryInput(Buffer, Offset, Count);
842
843 PlayerId = Input.ReadGuid();
844 PlayerRemoteAddress = IPAddress.Parse(Input.ReadString());
845 PlayerRemoteEndpoint = new IPEndPoint(PlayerRemoteAddress, Input.ReadUInt16());
846 }
847 catch (Exception)
848 {
849 await Connection.DisposeAsync();
850 return true;
851 }
852
853 Player Player = (Player)Connection.StateObject;
854 bool DisposeConnection = false;
855
856 lock (this.remotePlayersByEndpoint)
857 {
858 if (!this.playersById.TryGetValue(PlayerId, out Player Player2) || Player2.PlayerId != Player.PlayerId)
859 DisposeConnection = true;
860 else
861 Player.Connection = Connection;
862 }
863
864 if (DisposeConnection)
865 {
866 await Connection.DisposeAsync();
867 return true;
868 }
869
870 Connection.RemoteEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
871
872 Connection.OnReceived -= this.Connection_OnReceived;
873 Connection.OnReceived += this.Peer_OnReceived;
874 Connection.OnSent += this.Connection_OnSent;
875
876 BinaryOutput Output = new BinaryOutput();
877
878 Output.WriteGuid(this.localPlayer.PlayerId);
879 Output.WriteString(this.ExternalAddress.ToString());
880 Output.WriteUInt16((ushort)this.ExternalEndpoint.Port);
881
882 await Connection.SendTcp(true, Output.GetPacket());
883
884 await this.OnPlayerConnected.Raise(this, Player);
885
886 return true;
887 }
888
889 private async Task<bool> Connection_OnSent(object Sender, bool ConstantBuffer, byte[] Buffer, int Offset, int Count)
890 {
891 PeerConnection Connection = (PeerConnection)Sender;
892 Player Player = (Player)Connection.StateObject;
893 bool AllConnected;
894
895 Connection.OnSent -= this.Connection_OnSent;
896
897 bool DisposePlayerConnection = false;
898
899 lock (this.remotePlayersByEndpoint)
900 {
901 if (Player.Connection == Connection)
902 this.connectionCount++;
903 else
904 DisposePlayerConnection = true;
905
906 AllConnected = this.connectionCount + 1 == this.playerCount;
907 }
908
909 if (DisposePlayerConnection)
910 await Player.Connection.DisposeAsync();
911
912 if (AllConnected)
913 await this.SetState(MultiPlayerState.Ready);
914
915 return true;
916 }
917
918 private async Task MqttConnection_OnError(object Sender, Exception Exception)
919 {
920 this.exception = Exception;
921 await this.SetState(MultiPlayerState.Error);
922 }
923
924 private async Task MqttConnection_OnConnectionError(object Sender, Exception Exception)
925 {
926 this.exception = Exception;
927 await this.SetState(MultiPlayerState.Error);
928 }
929
933 public MultiPlayerState State => this.state;
934
935 internal async Task SetState(MultiPlayerState NewState)
936 {
937 if (this.state != NewState)
938 {
939 this.state = NewState;
940
941 switch (NewState)
942 {
943 case MultiPlayerState.Ready:
944 this.ready.Set();
945 break;
946
947 case MultiPlayerState.Error:
948 this.error.Set();
949 break;
950 }
951
952 await this.OnStateChange.Raise(this, NewState);
953 }
954 }
955
959 public event EventHandlerAsync<MultiPlayerState> OnStateChange = null;
960
964 public string ApplicationName => this.applicationName;
965
969 public IPAddress ExternalAddress
970 {
971 get { return this.p2pNetwork.ExternalAddress; }
972 }
973
977 public IPEndPoint ExternalEndpoint
978 {
979 get { return this.p2pNetwork.ExternalEndpoint; }
980 }
981
985 public IPAddress LocalAddress
986 {
987 get { return this.p2pNetwork.LocalAddress; }
988 }
989
993 public IPEndPoint LocalEndpoint
994 {
995 get { return this.p2pNetwork.LocalEndpoint; }
996 }
997
1001 public Exception Exception => this.exception;
1002
1007 public bool Wait()
1008 {
1009 return this.Wait(10000);
1010 }
1011
1017 public bool Wait(int TimeoutMilliseconds)
1018 {
1019 return WaitHandle.WaitAny(new WaitHandle[] { this.ready, this.error }, TimeoutMilliseconds) switch
1020 {
1021 0 => true,
1022 _ => false,
1023 };
1024 }
1025
1029 [Obsolete("Use the DisposeAsync() method.")]
1030 public void Dispose()
1031 {
1032 this.DisposeAsync().Wait();
1033 }
1034
1038 public async Task DisposeAsync()
1039 {
1040 await this.CloseMqtt();
1041
1042 await this.SetState(MultiPlayerState.Closed);
1043
1044 if (!(this.p2pNetwork is null))
1045 {
1046 await this.p2pNetwork.DisposeAsync();
1047 this.p2pNetwork = null;
1048 }
1049
1050 this.ready?.Dispose();
1051 this.ready = null;
1052
1053 this.error?.Dispose();
1054 this.error = null;
1055
1056 if (!(this.remotePlayersByEndpoint is null))
1057 {
1058 Player[] ToDispose;
1059
1060 lock (this.remotePlayersByEndpoint)
1061 {
1062 this.playersById.Clear();
1063 this.remotePlayersByIndex.Clear();
1064
1065 ToDispose = new Player[this.remotePlayersByEndpoint.Count];
1066 this.remotePlayersByEndpoint.Values.CopyTo(ToDispose, 0);
1067
1068 this.remotePlayersByEndpoint.Clear();
1069 this.remotePlayers = null;
1070 }
1071
1072 foreach (Player Player in ToDispose)
1073 {
1074 if (!(Player.Connection is null))
1076 }
1077 }
1078 }
1079
1080 private async Task CloseMqtt()
1081 {
1082 if (!(this.mqttConnection is null))
1083 {
1084 if (this.mqttConnection.State == MqttState.Connected)
1085 {
1086 BinaryOutput Output = new BinaryOutput();
1087 Output.WriteByte(2);
1088 Output.WriteString(this.applicationName);
1089 Output.WriteGuid(this.localPlayer.PlayerId);
1090
1091 this.mqttTerminatedPacketIdentifier = await this.mqttConnection.PUBLISH(this.mqttNegotiationTopic, MqttQualityOfService.AtLeastOnce, false, Output);
1092 this.mqttConnection.OnPublished += this.MqttConnection_OnPublished;
1093
1094#if LineListener
1095 ConsoleOut.WriteLine("Tx: BYE(" + this.localPlayer.ToString() + ")");
1096#endif
1097 }
1098 else
1099 {
1100 await this.mqttConnection.DisposeAsync();
1101 this.mqttConnection = null;
1102 }
1103 }
1104 }
1105
1106 private async Task MqttConnection_OnPublished(object Sender, ushort PacketIdentifier)
1107 {
1108 if (!(this.mqttConnection is null) && PacketIdentifier == this.mqttTerminatedPacketIdentifier)
1109 {
1110 await this.mqttConnection.DisposeAsync();
1111 this.mqttConnection = null;
1112 }
1113 }
1114
1118 public int PlayerCount => this.playerCount;
1119
1124 {
1125 get { return this.localPlayer.Index == 0; }
1126 }
1127
1128 }
1129}
Class that helps deserialize information stored in a binary packet.
Definition: BinaryInput.cs:12
int BytesLeft
Number of bytes left.
Definition: BinaryInput.cs:223
Guid ReadGuid()
Reads a GUID value.
Definition: BinaryInput.cs:244
ushort ReadUInt16()
Reads an unsignd 16-bit integer.
Definition: BinaryInput.cs:132
byte[] GetRemainingData()
Gets the remaining bytes.
Definition: BinaryInput.cs:229
ulong ReadUInt()
Reads a variable-length unsigned integer from the stream.
Definition: BinaryInput.cs:97
byte ReadByte()
Reads the next byte of the stream.
Definition: BinaryInput.cs:50
string ReadString()
Reads the next string of the stream.
Definition: BinaryInput.cs:78
Class that helps serialize information into a a binary packet.
Definition: BinaryOutput.cs:12
void WriteUInt16(ushort Value)
Writes a 16-bit integer to the stream.
void WriteGuid(Guid Guid)
Writes a GUID to the stream.
byte[] GetPacket()
Gets the binary packet written so far.
Definition: BinaryOutput.cs:84
void WriteUInt(ulong Value)
Writes a variable-length unsigned integer.
Definition: BinaryOutput.cs:95
void WriteByte(byte Value)
Writes a byte to the binary output packet.
Definition: BinaryOutput.cs:50
void WriteString(string Value)
Writes a string to the binary output packet.
Definition: BinaryOutput.cs:68
Manages an MQTT connection. Implements MQTT v3.1.1, as defined in http://docs.oasis-open....
Definition: MqttClient.cs:30
MqttState State
Current state of connection.
Definition: MqttClient.cs:821
Task< ushort > PUBLISH(string Topic, MqttQualityOfService QoS, bool Retain, byte[] Data)
Publishes information on a topic.
Definition: MqttClient.cs:876
Task< ushort > SUBSCRIBE(string Topic, MqttQualityOfService QoS)
Subscribes to information from a topic. Topics can include wildcards.
Definition: MqttClient.cs:1013
async Task DisposeAsync()
Closes the connection and disposes of all resources.
Definition: MqttClient.cs:1192
Information about content received from the MQTT server.
Definition: MqttContent.cs:9
BinaryInput DataInput
Data stream that can be used to parse incoming data.
Definition: MqttContent.cs:70
Event arguments for game data events.
Exception Exception
In case State=PeerToPeerNetworkState.Error, this exception object contains details about the error.
Task SendTcpTo(Player Player, bool ConstantBuffer, byte[] Packet)
Sends a packet to a specific player using TCP. Can only be done if State=MultiPlayerState....
Task SendTcpToAll(byte[] Packet)
Sends a packet to all remote players using TCP. Can only be done if State=MultiPlayerState....
EventHandlerAsync< GameDataEventArgs > OnGameDataReceived
Event raised when game data has been received from a player.
EventHandlerAsync< Player > OnPlayerDisconnected
Event raised when a player has been disconnected from the local macine.
Task SendUdpTo(Guid PlayerId, byte[] Packet, int IncludeNrPreviousPackets)
Sends a packet to a specific player using UDP. Can only be done if State=MultiPlayerState....
EventHandlerAsync< MultiPlayerState > OnStateChange
Event raised when the state of the peer-to-peer network changes.
virtual Task GameDataReceived(Player FromPlayer, PeerConnection Connection, byte[] Packet)
Is called when game data has been received.
EventHandlerAsync< Player > OnPlayerConnected
Event raised when a player has been connected to the local macine.
async Task SendTcpToAll(bool ConstantBuffer, byte[] Packet)
Sends a packet to all remote players using TCP. Can only be done if State=MultiPlayerState....
Exception Exception
In case State=MultiPlayerState.Error, this exception object contains details about the error.
async Task SendUdpToAll(byte[] Packet, int IncludeNrPreviousPackets)
Sends a packet to all remote players using UDP. Can only be done if State=MultiPlayerState....
async Task ConnectPlayers()
Creates inter-player peer-to-peer connections between known players.
Task SendTcpTo(Guid PlayerId, byte[] Packet)
Sends a packet to a specific player using TCP. Can only be done if State=MultiPlayerState....
EventHandlerAsync< Player > OnPlayerAvailable
Event raised when a new player is available.
bool LocalPlayerIsFirst
If the local player is the first player in the list of players. Can be used to determine which machin...
Task SendUdpTo(Player Player, byte[] Packet, int IncludeNrPreviousPackets)
Sends a packet to a specific player using UDP. Can only be done if State=MultiPlayerState....
MultiPlayerEnvironment(string ApplicationName, bool AllowMultipleApplicationsOnSameMachine, string MqttServer, int MqttPort, bool MqttTls, string MqttUserName, string MqttPassword, string MqttNegotiationTopic, int EstimatedMaxNrPlayers, Guid PlayerId, params KeyValuePair< string, string >[] PlayerMetaInfo)
Manages a multi-player environment.
bool Wait(int TimeoutMilliseconds)
Waits for the multi-player environment object to be ready to play.
Task SendTcpTo(Player Player, byte[] Packet)
Sends a packet to a specific player using TCP. Can only be done if State=MultiPlayerState....
bool Wait()
Waits for the multi-player environment object to be ready to play.
MultiPlayerState State
Current state of the multi-player environment.
Task SendTcpTo(Guid PlayerId, bool ConstantBuffer, byte[] Packet)
Sends a packet to a specific player using TCP. Can only be done if State=MultiPlayerState....
object StateObject
State object that applications can use to attach information to a connection.
void Start()
Starts receiving on the connection.
Task SendTcp(byte[] Packet)
Sends a packet to the peer at the other side of the TCP connection. Transmission is done asynchronous...
async Task DisposeAsync()
IDisposable.Dispose
Task SendUdp(byte[] Packet, int IncludeNrPreviousPackets)
Sends a packet to a peer using UDP. Transmission is done asynchronously and is buffered if a sending ...
Manages a peer-to-peer network that can receive connections from outside of a NAT-enabled firewall.
async Task< PeerConnection > ConnectToPeer(IPEndPoint RemoteEndPoint)
Connects to a peer in the peer-to-peer network. If the remote end point resides behind the same firew...
IPEndPoint ExternalEndpoint
External IP Endpoint.
override Task DisposeAsync()
IDisposable.Dispose
Class containing information about a player.
Definition: Player.cs:12
override string ToString()
Definition: Player.cs:121
PeerConnection Connection
Peer connection, if any.
Definition: Player.cs:115
IPEndPoint PublicEndpoint
Public Endpoint
Definition: Player.cs:48
Event arguments for UDP Datagram events.
Abstract base class for sniffers. Implements default method overloads.
Definition: SnifferBase.cs:15
static byte[] CloneSection(byte[] Data, int Offset, int Count)
Clones a section of a byte array.
Definition: SnifferBase.cs:165
Serializes output to System.Console.Out, and assures modules are not dead-locked in case the Console ...
Definition: ConsoleOut.cs:27
static void Write(string value)
Queues a value to be written to the console output.
Definition: ConsoleOut.cs:126
static void WriteLine()
Queues a value to be written to the console output.
Definition: ConsoleOut.cs:380
Interface for asynchronously disposable objects.
Definition: ImplTypes.g.cs:58
MqttQualityOfService
MQTT Quality of Service level.
MqttState
State of MQTT connection.
Definition: MqttState.cs:11
MultiPlayerState
State of multi-player environment.
PeerToPeerNetworkState
State of Peer-to-peer network.