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;
4using System.Collections.Generic;
5using System.Threading;
6using System.Threading.Tasks;
7using System.Net;
8using Waher.Events;
10#if LineListener
12#endif
13
15{
19 public enum MultiPlayerState
20 {
24 Created,
25
29 Reinitializing,
30
34 SearchingForGateway,
35
39 RegisteringApplicationInGateway,
40
44 FindingPlayers,
45
49 ConnectingPlayers,
50
54 Ready,
55
59 Error,
60
64 Closed
65 }
66
70 public class MultiPlayerEnvironment : IDisposable
71 {
72 private PeerToPeerNetwork p2pNetwork;
73 private MqttClient mqttConnection = null;
74 private MultiPlayerState state = MultiPlayerState.Created;
75 private ManualResetEvent ready = new ManualResetEvent(false);
76 private ManualResetEvent error = new ManualResetEvent(false);
77 private Exception exception;
78 private Player[] remotePlayers = new Player[0];
79 private int mqttTerminatedPacketIdentifier;
80 private int playerCount = 1;
81 private int connectionCount = 0;
82 private readonly Player localPlayer;
83 private readonly Dictionary<IPEndPoint, Player> remotePlayersByEndpoint = new Dictionary<IPEndPoint, Player>();
84 private readonly Dictionary<IPAddress, bool> remotePlayerIPs = new Dictionary<IPAddress, bool>();
85 private readonly Dictionary<Guid, Player> playersById = new Dictionary<Guid, Player>();
86 private readonly SortedDictionary<int, Player> remotePlayersByIndex = new SortedDictionary<int, Player>();
87 private readonly string applicationName;
88 private readonly string mqttServer;
89 private readonly int mqttPort;
90 private readonly string mqttNegotiationTopic;
91 private readonly string mqttUserName;
92 private readonly string mqttPassword;
93 private readonly bool mqttTls;
94
109 public MultiPlayerEnvironment(string ApplicationName, bool AllowMultipleApplicationsOnSameMachine,
110 string MqttServer, int MqttPort, bool MqttTls, string MqttUserName, string MqttPassword,
111 string MqttNegotiationTopic, int EstimatedMaxNrPlayers, Guid PlayerId, params KeyValuePair<string, string>[] PlayerMetaInfo)
112 {
113 this.localPlayer = new Player(PlayerId, new IPEndPoint(IPAddress.Any, 0), new IPEndPoint(IPAddress.Any, 0), PlayerMetaInfo);
114 this.playersById[PlayerId] = this.localPlayer;
115 this.applicationName = ApplicationName;
116
117 this.mqttServer = MqttServer;
118 this.mqttPort = MqttPort;
119 this.mqttTls = MqttTls;
120 this.mqttUserName = MqttUserName;
121 this.mqttPassword = MqttPassword;
122 this.mqttNegotiationTopic = MqttNegotiationTopic;
123
124 this.p2pNetwork = new PeerToPeerNetwork(AllowMultipleApplicationsOnSameMachine ? this.applicationName + " (" + PlayerId.ToString() + ")" :
125 this.applicationName, 0, 0, EstimatedMaxNrPlayers);
126 this.p2pNetwork.OnStateChange += this.P2PNetworkStateChange;
127 this.p2pNetwork.OnPeerConnected += this.P2pNetwork_OnPeerConnected;
128 this.p2pNetwork.OnUdpDatagramReceived += this.P2pNetwork_OnUdpDatagramReceived;
129 }
130
131 private async Task P2pNetwork_OnUdpDatagramReceived(object Sender, UdpDatagramEventArgs e)
132 {
134
135 lock (this.remotePlayersByEndpoint)
136 {
137 if (!this.remotePlayersByEndpoint.TryGetValue(e.RemoteEndpoint, out Player))
138 return;
139 }
140
141 if (!(Player.Connection is null))
142 await Player.Connection.UdpDatagramReceived(Sender, e);
143 }
144
145 private async Task P2PNetworkStateChange(object Sender, PeerToPeerNetworkState NewState)
146 {
147 switch (NewState)
148 {
149 case PeerToPeerNetworkState.Created:
150 await this.SetState(MultiPlayerState.Created);
151 break;
152
153 case PeerToPeerNetworkState.Reinitializing:
154 await this.SetState(MultiPlayerState.Reinitializing);
155 break;
156
157 case PeerToPeerNetworkState.SearchingForGateway:
158 await this.SetState(MultiPlayerState.SearchingForGateway);
159 break;
160
161 case PeerToPeerNetworkState.RegisteringApplicationInGateway:
162 await this.SetState(MultiPlayerState.RegisteringApplicationInGateway);
163 break;
164
165 case PeerToPeerNetworkState.Ready:
166 try
167 {
168 this.exception = null;
169
170 this.localPlayer.SetEndpoints(this.p2pNetwork.ExternalEndpoint, this.p2pNetwork.LocalEndpoint);
171
172 this.mqttConnection = new MqttClient(this.mqttServer, this.mqttPort, this.mqttTls, this.mqttUserName, this.mqttPassword);
173 this.mqttConnection.OnConnectionError += this.MqttConnection_OnConnectionError;
174 this.mqttConnection.OnError += this.MqttConnection_OnError;
175 this.mqttConnection.OnStateChanged += this.MqttConnection_OnStateChanged;
176 this.mqttConnection.OnContentReceived += this.MqttConnection_OnContentReceived;
177
178 await this.SetState(MultiPlayerState.FindingPlayers);
179 }
180 catch (Exception ex)
181 {
182 this.exception = ex;
183 await this.SetState(MultiPlayerState.Error);
184 }
185 break;
186
187 case PeerToPeerNetworkState.Error:
188 this.exception = this.p2pNetwork.Exception;
189 await this.SetState(MultiPlayerState.Error);
190 break;
191
192 case PeerToPeerNetworkState.Closed:
193 await this.SetState(MultiPlayerState.Closed);
194 break;
195 }
196 }
197
198 private async Task MqttConnection_OnStateChanged(object Sender, MqttState NewState)
199 {
200 if (NewState == MqttState.Connected)
201 {
202 await this.mqttConnection.SUBSCRIBE(this.mqttNegotiationTopic);
203
204 BinaryOutput Output = new BinaryOutput();
205 Output.WriteByte(0);
206 Output.WriteString(this.applicationName);
207
208 this.localPlayer.SetEndpoints(this.p2pNetwork.ExternalEndpoint, this.p2pNetwork.LocalEndpoint);
209 this.Serialize(this.localPlayer, Output);
210
211 await this.mqttConnection.PUBLISH(this.mqttNegotiationTopic, MqttQualityOfService.AtLeastOnce, false, Output);
212
213#if LineListener
214 ConsoleOut.WriteLine("Tx: HELLO(" + this.localPlayer.ToString() + ")");
215#endif
216 }
217 }
218
219 private void Serialize(Player Player, BinaryOutput Output)
220 {
221 Output.WriteString(Player.PublicEndpoint.Address.ToString());
222 Output.WriteUInt16((ushort)Player.PublicEndpoint.Port);
223
224 Output.WriteString(Player.LocalEndpoint.Address.ToString());
225 Output.WriteUInt16((ushort)Player.LocalEndpoint.Port);
226
227 Output.WriteGuid(Player.PlayerId);
228 Output.WriteUInt((uint)Player.Count);
229
230 foreach (KeyValuePair<string, string> P in Player)
231 {
232 Output.WriteString(P.Key);
233 Output.WriteString(P.Value);
234 }
235 }
236
237 private Player Deserialize(BinaryInput Input)
238 {
239 IPAddress PublicAddress = IPAddress.Parse(Input.ReadString());
240 ushort PublicPort = Input.ReadUInt16();
241 IPEndPoint PublicEndpoint = new IPEndPoint(PublicAddress, PublicPort);
242
243 IPAddress LocalAddress = IPAddress.Parse(Input.ReadString());
244 ushort LocalPort = Input.ReadUInt16();
245 IPEndPoint LocalEndpoint = new IPEndPoint(LocalAddress, LocalPort);
246
247 Guid PlayerId = Input.ReadGuid();
248 bool LocalPlayer = PlayerId == this.localPlayer.PlayerId;
249 int i, c = (int)Input.ReadUInt();
250 KeyValuePair<string, string>[] PlayerMetaInfo = LocalPlayer ? null : new KeyValuePair<string, string>[c];
251 string Key, Value;
252
253 for (i = 0; i < c; i++)
254 {
255 Key = Input.ReadString();
256 Value = Input.ReadString();
257 if (!LocalPlayer)
258 PlayerMetaInfo[i] = new KeyValuePair<string, string>(Key, Value);
259 }
260
261 if (LocalPlayer)
262 return null;
263 else
264 return new Player(PlayerId, PublicEndpoint, LocalEndpoint, PlayerMetaInfo);
265 }
266
267 private async Task MqttConnection_OnContentReceived(object Sender, MqttContent Content)
268 {
269 BinaryInput Input = Content.DataInput;
270 byte Command = Input.ReadByte();
271
272 switch (Command)
273 {
274 case 0: // Hello
275 string ApplicationName = Input.ReadString();
276 if (ApplicationName != this.applicationName)
277 break;
278
279 Player Player = this.Deserialize(Input);
280 if (Player is null)
281 break;
282
283#if LineListener
284 ConsoleOut.WriteLine("Rx: HELLO(" + Player.ToString() + ")");
285#endif
286 IPEndPoint ExpectedEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
287
288 lock (this.remotePlayersByEndpoint)
289 {
290 this.remotePlayersByEndpoint[ExpectedEndpoint] = Player;
291 this.remotePlayerIPs[ExpectedEndpoint.Address] = true;
292 this.playersById[Player.PlayerId] = Player;
293
294 this.UpdateRemotePlayersLocked();
295 }
296
297 await this.OnPlayerAvailable.Raise(this, Player);
298 break;
299
300 case 1: // Interconnect
301 ApplicationName = Input.ReadString();
302 if (ApplicationName != this.applicationName)
303 break;
304
305 Player = this.Deserialize(Input);
306 if (Player is null)
307 break;
308
309#if LineListener
310 ConsoleOut.Write("Rx: INTERCONNECT(" + Player.ToString());
311#endif
312 int Index = 0;
313 int i, c;
314 LinkedList<Player> Players = new LinkedList<Player>();
315 bool LocalPlayerIncluded = false;
316
317 Player.Index = Index++;
318 Players.AddLast(Player);
319
320 c = (int)Input.ReadUInt();
321 for (i = 0; i < c; i++)
322 {
323 Player = this.Deserialize(Input);
324 if (Player is null)
325 {
326#if LineListener
327 ConsoleOut.Write("," + this.localPlayer.ToString());
328#endif
329 this.localPlayer.Index = Index++;
330 LocalPlayerIncluded = true;
331 }
332 else
333 {
334#if LineListener
335 ConsoleOut.Write("," + Player.ToString());
336#endif
337 Player.Index = Index++;
338 Players.AddLast(Player);
339 }
340 }
341
342#if LineListener
344#endif
345 if (!LocalPlayerIncluded)
346 break;
347
348 await this.mqttConnection.DisposeAsync();
349 this.mqttConnection = null;
350
351 lock (this.remotePlayersByEndpoint)
352 {
353 this.remotePlayersByEndpoint.Clear();
354 this.remotePlayerIPs.Clear();
355 this.remotePlayersByIndex.Clear();
356 this.playersById.Clear();
357
358 this.remotePlayersByIndex[this.localPlayer.Index] = this.localPlayer;
359 this.playersById[this.localPlayer.PlayerId] = this.localPlayer;
360
361 foreach (Player Player2 in Players)
362 {
363 ExpectedEndpoint = Player2.GetExpectedEndpoint(this.p2pNetwork);
364
365 this.remotePlayersByIndex[Player2.Index] = Player2;
366 this.remotePlayersByEndpoint[ExpectedEndpoint] = Player2;
367 this.remotePlayerIPs[ExpectedEndpoint.Address] = true;
368 this.playersById[Player2.PlayerId] = Player2;
369 }
370
371 this.UpdateRemotePlayersLocked();
372 }
373
374 await this.SetState(MultiPlayerState.ConnectingPlayers);
375 await this.StartConnecting();
376 break;
377
378 case 2: // Bye
379 ApplicationName = Input.ReadString();
380 if (ApplicationName != this.applicationName)
381 break;
382
383 Guid PlayerId = Input.ReadGuid();
384 lock (this.remotePlayersByEndpoint)
385 {
386 if (!this.playersById.TryGetValue(PlayerId, out Player))
387 break;
388
389#if LineListener
390 ConsoleOut.WriteLine("Rx: BYE(" + Player.ToString() + ")");
391#endif
392 ExpectedEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
393
394 this.playersById.Remove(PlayerId);
395 this.remotePlayersByEndpoint.Remove(ExpectedEndpoint);
396 this.remotePlayersByIndex.Remove(Player.Index);
397
398 IPAddress ExpectedAddress = ExpectedEndpoint.Address;
399 bool AddressFound = false;
400
401 foreach (IPEndPoint EP in this.remotePlayersByEndpoint.Keys)
402 {
403 if (IPAddress.Equals(EP.Address, ExpectedAddress))
404 {
405 AddressFound = true;
406 break;
407 }
408 }
409
410 if (!AddressFound)
411 this.remotePlayerIPs.Remove(ExpectedAddress);
412
413 this.UpdateRemotePlayersLocked();
414 }
415 break;
416 }
417 }
418
419 private void UpdateRemotePlayersLocked()
420 {
421 int c = this.remotePlayersByEndpoint.Count;
422
423 this.playerCount = 1 + c;
424 this.remotePlayers = new Player[c];
425 this.remotePlayersByEndpoint.Values.CopyTo(this.remotePlayers, 0);
426 }
427
428 private async Task P2pNetwork_OnPeerConnected(object Listener, PeerConnection Peer)
429 {
430 IPEndPoint Endpoint = (IPEndPoint)Peer.Tcp.Client.Client.RemoteEndPoint;
431
432#if LineListener
433 ConsoleOut.WriteLine("Receiving connection from " + Endpoint.ToString());
434#endif
435
436 bool Dispose = false;
437
438 lock (this.remotePlayersByEndpoint)
439 {
440 if (!this.remotePlayerIPs.ContainsKey(Endpoint.Address))
441 Dispose = true;
442 }
443
444 if (Dispose)
445 {
446 await Peer.DisposeAsync();
447 return;
448 }
449
450 Peer.OnClosed += this.Peer_OnClosed;
451 Peer.OnReceived += this.Peer_OnReceived;
452
453 BinaryOutput Output = new BinaryOutput();
454
455 Output.WriteGuid(this.localPlayer.PlayerId);
456 Output.WriteString(this.ExternalEndpoint.Address.ToString());
457 Output.WriteUInt16((ushort)this.ExternalEndpoint.Port);
458
459 await Peer.SendTcp(Output.GetPacket());
460 }
461
462 private async Task<bool> Peer_OnReceived(object Sender, byte[] Buffer, int Offset, int Count)
463 {
464 PeerConnection Connection = (PeerConnection)Sender;
465 Player Player;
466 byte[] Packet;
467
468 if (Connection.StateObject is null)
469 {
470 BinaryInput Input = new BinaryInput(BinaryTcpClient.ToArray(Buffer, Offset, Count));
471 Guid PlayerId;
472 IPAddress PlayerRemoteAddress;
473 IPEndPoint PlayerRemoteEndpoint;
474
475 try
476 {
477 PlayerId = Input.ReadGuid();
478 PlayerRemoteAddress = IPAddress.Parse(Input.ReadString());
479 PlayerRemoteEndpoint = new IPEndPoint(PlayerRemoteAddress, Input.ReadUInt16());
480 }
481 catch (Exception)
482 {
483 if (!(Connection is null))
484 await Connection.DisposeAsync();
485
486 return true;
487 }
488
489 if (Input.BytesLeft == 0)
490 Packet = null;
491 else
492 Packet = Input.GetRemainingData();
493
494 bool AllConnected = false;
495 bool DisposeConnection = false;
496 PeerConnection ObsoleteConnection = null;
497
498 lock (this.remotePlayersByEndpoint)
499 {
500 if (!this.playersById.TryGetValue(PlayerId, out Player))
501 DisposeConnection = true;
502 else
503 {
504 if (Player.Connection is null)
505 this.connectionCount++;
506 else
507 ObsoleteConnection = Player.Connection;
508
509 Player.Connection = Connection;
510 Connection.StateObject = Player;
511 Connection.RemoteEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
512
513 AllConnected = this.connectionCount + 1 == this.playerCount;
514 }
515 }
516
517 if (DisposeConnection)
518 {
519 if (!(Connection is null))
520 await Connection.DisposeAsync();
521
522 return true;
523 }
524
525 if (!(ObsoleteConnection is null))
526 await ObsoleteConnection.DisposeAsync();
527
528 await this.OnPlayerConnected.Raise(this, Player);
529
530 if (AllConnected)
531 await this.SetState(MultiPlayerState.Ready);
532
533 if (Packet is null)
534 return true;
535 }
536 else
537 {
538 Player = (Player)Connection.StateObject;
539 Packet = BinaryTcpClient.ToArray(Buffer, Offset, Count);
540 }
541
542 await this.GameDataReceived(Player, Connection, Packet);
543
544 return true;
545 }
546
553 protected virtual Task GameDataReceived(Player FromPlayer, PeerConnection Connection, byte[] Packet)
554 {
555 return this.OnGameDataReceived.Raise(this, new GameDataEventArgs(FromPlayer, Connection, Packet));
556 }
557
561 public event EventHandlerAsync<GameDataEventArgs> OnGameDataReceived = null;
562
568 public async Task SendTcpToAll(byte[] Packet)
569 {
570 if (this.state != MultiPlayerState.Ready)
571 throw new Exception("The multiplayer environment is not ready to exchange data between players.");
572
573 PeerConnection Connection;
574 foreach (Player Player in this.remotePlayers)
575 {
576 if (!((Connection = Player.Connection) is null))
577 await Connection.SendTcp(Packet);
578 }
579 }
580
587 public Task SendTcpTo(Player Player, byte[] Packet)
588 {
589 if (this.state != MultiPlayerState.Ready)
590 throw new Exception("The multiplayer environment is not ready to exchange data between players.");
591
592 PeerConnection Connection = Player.Connection;
593 return Connection?.SendTcp(Packet) ?? Task.CompletedTask;
594 }
595
602 public Task SendTcpTo(Guid PlayerId, byte[] Packet)
603 {
605
606 lock (this.remotePlayersByEndpoint)
607 {
608 if (!this.playersById.TryGetValue(PlayerId, out Player))
609 throw new ArgumentException("No player with that ID.", nameof(PlayerId));
610 }
611
612 PeerConnection Connection = Player.Connection;
613 return Connection?.SendTcp(Packet) ?? Task.CompletedTask;
614 }
615
623 public async Task SendUdpToAll(byte[] Packet, int IncludeNrPreviousPackets)
624 {
625 if (this.state != MultiPlayerState.Ready)
626 throw new Exception("The multiplayer environment is not ready to exchange data between players.");
627
628 PeerConnection Connection;
629 foreach (Player Player in this.remotePlayers)
630 {
631 if (!((Connection = Player.Connection) is null))
632 await Connection.SendUdp(Packet, IncludeNrPreviousPackets);
633 }
634 }
635
644 public Task SendUdpTo(Player Player, byte[] Packet, int IncludeNrPreviousPackets)
645 {
646 if (this.state != MultiPlayerState.Ready)
647 throw new Exception("The multiplayer environment is not ready to exchange data between players.");
648
649 PeerConnection Connection = Player.Connection;
650 return Connection?.SendUdp(Packet, IncludeNrPreviousPackets) ?? Task.CompletedTask;
651 }
652
661 public Task SendUdpTo(Guid PlayerId, byte[] Packet, int IncludeNrPreviousPackets)
662 {
664
665 lock (this.remotePlayersByEndpoint)
666 {
667 if (!this.playersById.TryGetValue(PlayerId, out Player))
668 throw new ArgumentException("No player with that ID.", nameof(PlayerId));
669 }
670
671 PeerConnection Connection = Player.Connection;
672 return Connection?.SendUdp(Packet, IncludeNrPreviousPackets) ?? Task.CompletedTask;
673 }
674
675 private async Task Peer_OnClosed(object Sender, EventArgs e)
676 {
677 PeerConnection Connection = (PeerConnection)Sender;
678 Player Player = (Player)Connection.StateObject;
679 if (Player is null)
680 return;
681
682 if (Player.Connection != Connection)
683 return;
684
685 lock (this.remotePlayersByEndpoint)
686 {
687 Player.Connection = null;
688 this.connectionCount--;
689
690 Connection.StateObject = null;
691 }
692
693 await this.OnPlayerDisconnected.Raise(this, Player);
694 }
695
699 public event EventHandlerAsync<Player> OnPlayerAvailable = null;
700
704 public event EventHandlerAsync<Player> OnPlayerConnected = null;
705
709 public event EventHandlerAsync<Player> OnPlayerDisconnected = null;
710
714 public async Task ConnectPlayers()
715 {
716 if (this.state != MultiPlayerState.FindingPlayers)
717 throw new Exception("The multiplayer environment is not in the state of finding players.");
718
719 await this.SetState(MultiPlayerState.ConnectingPlayers);
720
721 int Index = 0;
722 BinaryOutput Output = new BinaryOutput();
723 Output.WriteByte(1);
724 Output.WriteString(this.applicationName);
725 this.localPlayer.Index = Index++;
726 this.Serialize(this.localPlayer, Output);
727
728#if LineListener
729 ConsoleOut.Write("Tx: INTERCONNECT(" + this.localPlayer.ToString());
730#endif
731 lock (this.remotePlayersByEndpoint)
732 {
733 Output.WriteUInt((uint)this.remotePlayersByEndpoint.Count);
734
735 foreach (Player Player in this.remotePlayersByEndpoint.Values)
736 {
737 Player.Index = Index++;
738 this.Serialize(Player, Output);
739
740#if LineListener
742#endif
743 }
744 }
745
746 this.mqttTerminatedPacketIdentifier = await this.mqttConnection.PUBLISH(this.mqttNegotiationTopic, MqttQualityOfService.AtLeastOnce, false, Output);
747 this.mqttConnection.OnPublished += this.MqttConnection_OnPublished;
748
749#if LineListener
751#endif
752 await this.StartConnecting();
753 }
754
755 private async Task StartConnecting()
756 {
757#if LineListener
758 ConsoleOut.WriteLine("Current player has index " + this.localPlayer.Index.ToString());
759#endif
760 if (this.remotePlayers.Length == 0)
761 await this.SetState(MultiPlayerState.Ready);
762 else
763 {
764 foreach (Player Player in this.remotePlayers)
765 {
766 if (Player.Index < this.localPlayer.Index)
767 {
768#if LineListener
769 ConsoleOut.WriteLine("Connecting to " + Player.ToString() + " (index " + Player.Index.ToString() + ")");
770#endif
771 PeerConnection Connection = await this.p2pNetwork.ConnectToPeer(Player.PublicEndpoint);
772
773 Connection.StateObject = Player;
774 Connection.OnClosed += this.Peer_OnClosed;
775 Connection.OnReceived += this.Connection_OnReceived;
776
777 Connection.Start();
778 }
779 else
780 {
781#if LineListener
782 ConsoleOut.WriteLine("Waiting for connection from " + Player.ToString() + " (index " + Player.Index.ToString() + ")");
783#endif
784 }
785 }
786 }
787 }
788
789 private async Task<bool> Connection_OnReceived(object Sender, byte[] Buffer, int Offset, int Count)
790 {
791 PeerConnection Connection = (PeerConnection)Sender;
792 Guid PlayerId;
793 IPAddress PlayerRemoteAddress;
794 IPEndPoint PlayerRemoteEndpoint;
795
796 try
797 {
798 BinaryInput Input = new BinaryInput(BinaryTcpClient.ToArray(Buffer, Offset, Count));
799
800 PlayerId = Input.ReadGuid();
801 PlayerRemoteAddress = IPAddress.Parse(Input.ReadString());
802 PlayerRemoteEndpoint = new IPEndPoint(PlayerRemoteAddress, Input.ReadUInt16());
803 }
804 catch (Exception)
805 {
806 await Connection.DisposeAsync();
807 return true;
808 }
809
810 Player Player = (Player)Connection.StateObject;
811 bool DisposeConnection = false;
812
813 lock (this.remotePlayersByEndpoint)
814 {
815 if (!this.playersById.TryGetValue(PlayerId, out Player Player2) || Player2.PlayerId != Player.PlayerId)
816 DisposeConnection = true;
817 else
818 Player.Connection = Connection;
819 }
820
821 if (DisposeConnection)
822 {
823 await Connection.DisposeAsync();
824 return true;
825 }
826
827 Connection.RemoteEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
828
829 Connection.OnReceived -= this.Connection_OnReceived;
830 Connection.OnReceived += this.Peer_OnReceived;
831 Connection.OnSent += this.Connection_OnSent;
832
833 BinaryOutput Output = new BinaryOutput();
834
835 Output.WriteGuid(this.localPlayer.PlayerId);
836 Output.WriteString(this.ExternalAddress.ToString());
837 Output.WriteUInt16((ushort)this.ExternalEndpoint.Port);
838
839 await Connection.SendTcp(Output.GetPacket());
840
841 await this.OnPlayerConnected.Raise(this, Player);
842
843 return true;
844 }
845
846 private async Task<bool> Connection_OnSent(object Sender, byte[] Buffer, int Offset, int Count)
847 {
848 PeerConnection Connection = (PeerConnection)Sender;
849 Player Player = (Player)Connection.StateObject;
850 bool AllConnected;
851
852 Connection.OnSent -= this.Connection_OnSent;
853
854 bool DisposePlayerConnection = false;
855
856 lock (this.remotePlayersByEndpoint)
857 {
858 if (Player.Connection == Connection)
859 this.connectionCount++;
860 else
861 DisposePlayerConnection = true;
862
863 AllConnected = this.connectionCount + 1 == this.playerCount;
864 }
865
866 if (DisposePlayerConnection)
867 await Player.Connection.DisposeAsync();
868
869 if (AllConnected)
870 await this.SetState(MultiPlayerState.Ready);
871
872 return true;
873 }
874
875 private async Task MqttConnection_OnError(object Sender, Exception Exception)
876 {
877 this.exception = Exception;
878 await this.SetState(MultiPlayerState.Error);
879 }
880
881 private async Task MqttConnection_OnConnectionError(object Sender, Exception Exception)
882 {
883 this.exception = Exception;
884 await this.SetState(MultiPlayerState.Error);
885 }
886
890 public MultiPlayerState State => this.state;
891
892 internal async Task SetState(MultiPlayerState NewState)
893 {
894 if (this.state != NewState)
895 {
896 this.state = NewState;
897
898 switch (NewState)
899 {
900 case MultiPlayerState.Ready:
901 this.ready.Set();
902 break;
903
904 case MultiPlayerState.Error:
905 this.error.Set();
906 break;
907 }
908
909 await this.OnStateChange.Raise(this, NewState);
910 }
911 }
912
916 public event EventHandlerAsync<MultiPlayerState> OnStateChange = null;
917
921 public string ApplicationName => this.applicationName;
922
926 public IPAddress ExternalAddress
927 {
928 get { return this.p2pNetwork.ExternalAddress; }
929 }
930
934 public IPEndPoint ExternalEndpoint
935 {
936 get { return this.p2pNetwork.ExternalEndpoint; }
937 }
938
942 public IPAddress LocalAddress
943 {
944 get { return this.p2pNetwork.LocalAddress; }
945 }
946
950 public IPEndPoint LocalEndpoint
951 {
952 get { return this.p2pNetwork.LocalEndpoint; }
953 }
954
958 public Exception Exception => this.exception;
959
964 public bool Wait()
965 {
966 return this.Wait(10000);
967 }
968
974 public bool Wait(int TimeoutMilliseconds)
975 {
976 switch (WaitHandle.WaitAny(new WaitHandle[] { this.ready, this.error }, TimeoutMilliseconds))
977 {
978 case 0:
979 return true;
980
981 case 1:
982 default:
983 return false;
984 }
985 }
986
990 [Obsolete("Use the DisposeAsync() method.")]
991 public async void Dispose()
992 {
993 try
994 {
995 await this.DisposeAsync();
996 }
997 catch (Exception ex)
998 {
999 Log.Exception(ex);
1000 }
1001 }
1002
1006 public async Task DisposeAsync()
1007 {
1008 await this.CloseMqtt();
1009
1010 await this.SetState(MultiPlayerState.Closed);
1011
1012 if (!(this.p2pNetwork is null))
1013 {
1014 await this.p2pNetwork.DisposeAsync();
1015 this.p2pNetwork = null;
1016 }
1017
1018 this.ready?.Dispose();
1019 this.ready = null;
1020
1021 this.error?.Dispose();
1022 this.error = null;
1023
1024 if (!(this.remotePlayersByEndpoint is null))
1025 {
1026 Player[] ToDispose;
1027
1028 lock (this.remotePlayersByEndpoint)
1029 {
1030 this.playersById.Clear();
1031 this.remotePlayersByIndex.Clear();
1032
1033 ToDispose = new Player[this.remotePlayersByEndpoint.Count];
1034 this.remotePlayersByEndpoint.Values.CopyTo(ToDispose, 0);
1035
1036 this.remotePlayersByEndpoint.Clear();
1037 this.remotePlayers = null;
1038 }
1039
1040 foreach (Player Player in ToDispose)
1041 {
1042 if (!(Player.Connection is null))
1044 }
1045 }
1046 }
1047
1048 private async Task CloseMqtt()
1049 {
1050 if (!(this.mqttConnection is null))
1051 {
1052 if (this.mqttConnection.State == MqttState.Connected)
1053 {
1054 BinaryOutput Output = new BinaryOutput();
1055 Output.WriteByte(2);
1056 Output.WriteString(this.applicationName);
1057 Output.WriteGuid(this.localPlayer.PlayerId);
1058
1059 this.mqttTerminatedPacketIdentifier = await this.mqttConnection.PUBLISH(this.mqttNegotiationTopic, MqttQualityOfService.AtLeastOnce, false, Output);
1060 this.mqttConnection.OnPublished += this.MqttConnection_OnPublished;
1061
1062#if LineListener
1063 ConsoleOut.WriteLine("Tx: BYE(" + this.localPlayer.ToString() + ")");
1064#endif
1065 }
1066 else
1067 {
1068 await this.mqttConnection.DisposeAsync();
1069 this.mqttConnection = null;
1070 }
1071 }
1072 }
1073
1074 private async Task MqttConnection_OnPublished(object Sender, ushort PacketIdentifier)
1075 {
1076 if (!(this.mqttConnection is null) && PacketIdentifier == this.mqttTerminatedPacketIdentifier)
1077 {
1078 await this.mqttConnection.DisposeAsync();
1079 this.mqttConnection = null;
1080 }
1081 }
1082
1086 public int PlayerCount => this.playerCount;
1087
1092 {
1093 get { return this.localPlayer.Index == 0; }
1094 }
1095
1096 }
1097}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
Implements a binary TCP Client, by encapsulating a TcpClient. It also makes the use of TcpClient safe...
static byte[] ToArray(byte[] Buffer, int Offset, int Count)
Converts a binary subset of a buffer into an array.
Class that helps deserialize information stored in a binary packet.
Definition: BinaryInput.cs:12
int BytesLeft
Number of bytes left.
Definition: BinaryInput.cs:216
Guid ReadGuid()
Reads a GUID value.
Definition: BinaryInput.cs:239
ushort ReadUInt16()
Reads an unsignd 16-bit integer.
Definition: BinaryInput.cs:121
byte[] GetRemainingData()
Gets the remaining bytes.
Definition: BinaryInput.cs:224
ulong ReadUInt()
Reads a variable-length unsigned integer from the stream.
Definition: BinaryInput.cs:86
byte ReadByte()
Reads the next byte of the stream.
Definition: BinaryInput.cs:39
string ReadString()
Reads the next string of the stream.
Definition: BinaryInput.cs:67
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:26
MqttState State
Current state of connection.
Definition: MqttClient.cs:812
Task< ushort > PUBLISH(string Topic, MqttQualityOfService QoS, bool Retain, byte[] Data)
Publishes information on a topic.
Definition: MqttClient.cs:851
Task< ushort > SUBSCRIBE(string Topic, MqttQualityOfService QoS)
Subscribes to information from a topic. Topics can include wildcards.
Definition: MqttClient.cs:988
async Task DisposeAsync()
Closes the connection and disposes of all resources.
Definition: MqttClient.cs:1174
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.
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.
async Task SendTcpToAll(byte[] Packet)
Sends a packet to all remote players using TCP. Can only be done if State=MultiPlayerState....
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.
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.
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...
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.
Serializes output to System.Console.Out, and assures modules are not dead-locked in case the Console ...
Definition: ConsoleOut.cs:26
static void Write(string value)
Queues a value to be written to the console output.
Definition: ConsoleOut.cs:110
static void WriteLine()
Queues a value to be written to the console output.
Definition: ConsoleOut.cs:364
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.