4using System.Collections.Generic;
6using System.Threading.Tasks;
39 RegisteringApplicationInGateway,
75 private ManualResetEvent ready =
new ManualResetEvent(
false);
76 private ManualResetEvent error =
new ManualResetEvent(
false);
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;
110 string MqttServer,
int MqttPort,
bool MqttTls,
string MqttUserName,
string MqttPassword,
111 string MqttNegotiationTopic,
int EstimatedMaxNrPlayers, Guid PlayerId, params KeyValuePair<string, string>[] PlayerMetaInfo)
113 this.localPlayer =
new Player(PlayerId,
new IPEndPoint(IPAddress.Any, 0),
new IPEndPoint(IPAddress.Any, 0), PlayerMetaInfo);
114 this.playersById[PlayerId] = this.localPlayer;
117 this.mqttServer = MqttServer;
118 this.mqttPort = MqttPort;
119 this.mqttTls = MqttTls;
120 this.mqttUserName = MqttUserName;
121 this.mqttPassword = MqttPassword;
122 this.mqttNegotiationTopic = MqttNegotiationTopic;
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;
135 lock (this.remotePlayersByEndpoint)
142 await Player.
Connection.UdpDatagramReceived(Sender, e);
168 this.exception =
null;
170 this.localPlayer.SetEndpoints(this.p2pNetwork.
ExternalEndpoint,
this.p2pNetwork.LocalEndpoint);
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;
188 this.exception = this.p2pNetwork.
Exception;
198 private async Task MqttConnection_OnStateChanged(
object Sender,
MqttState NewState)
202 await this.mqttConnection.
SUBSCRIBE(this.mqttNegotiationTopic);
208 this.localPlayer.SetEndpoints(this.p2pNetwork.
ExternalEndpoint,
this.p2pNetwork.LocalEndpoint);
209 this.Serialize(this.localPlayer, Output);
219 private void Serialize(Player Player,
BinaryOutput Output)
221 Output.
WriteString(Player.PublicEndpoint.Address.ToString());
222 Output.
WriteUInt16((ushort)Player.PublicEndpoint.Port);
224 Output.
WriteString(Player.LocalEndpoint.Address.ToString());
225 Output.
WriteUInt16((ushort)Player.LocalEndpoint.Port);
230 foreach (KeyValuePair<string, string> P
in Player)
239 IPAddress PublicAddress = IPAddress.Parse(Input.
ReadString());
241 IPEndPoint PublicEndpoint =
new IPEndPoint(PublicAddress, PublicPort);
248 bool LocalPlayer = PlayerId == this.localPlayer.PlayerId;
250 KeyValuePair<string, string>[] PlayerMetaInfo = LocalPlayer ? null :
new KeyValuePair<string, string>[c];
253 for (i = 0; i < c; i++)
258 PlayerMetaInfo[i] =
new KeyValuePair<string, string>(Key, Value);
264 return new Player(PlayerId, PublicEndpoint,
LocalEndpoint, PlayerMetaInfo);
267 private async Task MqttConnection_OnContentReceived(
object Sender,
MqttContent Content)
279 Player Player = this.Deserialize(Input);
286 IPEndPoint ExpectedEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
288 lock (this.remotePlayersByEndpoint)
290 this.remotePlayersByEndpoint[ExpectedEndpoint] = Player;
291 this.remotePlayerIPs[ExpectedEndpoint.Address] =
true;
292 this.playersById[Player.PlayerId] = Player;
294 this.UpdateRemotePlayersLocked();
305 Player = this.Deserialize(Input);
314 LinkedList<Player> Players =
new LinkedList<Player>();
315 bool LocalPlayerIncluded =
false;
317 Player.Index = Index++;
318 Players.AddLast(Player);
321 for (i = 0; i < c; i++)
323 Player = this.Deserialize(Input);
329 this.localPlayer.Index = Index++;
330 LocalPlayerIncluded =
true;
337 Player.Index = Index++;
338 Players.AddLast(Player);
345 if (!LocalPlayerIncluded)
349 this.mqttConnection =
null;
351 lock (this.remotePlayersByEndpoint)
353 this.remotePlayersByEndpoint.Clear();
354 this.remotePlayerIPs.Clear();
355 this.remotePlayersByIndex.Clear();
356 this.playersById.Clear();
358 this.remotePlayersByIndex[this.localPlayer.Index] = this.localPlayer;
359 this.playersById[this.localPlayer.PlayerId] = this.localPlayer;
361 foreach (Player Player2
in Players)
363 ExpectedEndpoint = Player2.GetExpectedEndpoint(this.p2pNetwork);
365 this.remotePlayersByIndex[Player2.Index] = Player2;
366 this.remotePlayersByEndpoint[ExpectedEndpoint] = Player2;
367 this.remotePlayerIPs[ExpectedEndpoint.Address] =
true;
368 this.playersById[Player2.PlayerId] = Player2;
371 this.UpdateRemotePlayersLocked();
375 await this.StartConnecting();
384 lock (this.remotePlayersByEndpoint)
386 if (!this.playersById.TryGetValue(PlayerId, out Player))
392 ExpectedEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
394 this.playersById.Remove(PlayerId);
395 this.remotePlayersByEndpoint.Remove(ExpectedEndpoint);
396 this.remotePlayersByIndex.Remove(Player.Index);
398 IPAddress ExpectedAddress = ExpectedEndpoint.Address;
399 bool AddressFound =
false;
401 foreach (IPEndPoint EP
in this.remotePlayersByEndpoint.Keys)
403 if (IPAddress.Equals(EP.Address, ExpectedAddress))
411 this.remotePlayerIPs.Remove(ExpectedAddress);
413 this.UpdateRemotePlayersLocked();
419 private void UpdateRemotePlayersLocked()
421 int c = this.remotePlayersByEndpoint.Count;
423 this.playerCount = 1 + c;
424 this.remotePlayers =
new Player[c];
425 this.remotePlayersByEndpoint.Values.CopyTo(this.remotePlayers, 0);
428 private async Task P2pNetwork_OnPeerConnected(
object Listener, PeerConnection Peer)
430 IPEndPoint Endpoint = (IPEndPoint)Peer.Tcp.Client.Client.RemoteEndPoint;
438 lock (this.remotePlayersByEndpoint)
440 if (!this.remotePlayerIPs.ContainsKey(Endpoint.Address))
446 await Peer.DisposeAsync();
450 Peer.OnClosed += this.Peer_OnClosed;
451 Peer.OnReceived += this.Peer_OnReceived;
455 Output.
WriteGuid(this.localPlayer.PlayerId);
462 private async Task<bool> Peer_OnReceived(
object Sender,
byte[] Buffer,
int Offset,
int Count)
464 PeerConnection Connection = (PeerConnection)Sender;
468 if (Connection.StateObject is
null)
472 IPAddress PlayerRemoteAddress;
473 IPEndPoint PlayerRemoteEndpoint;
478 PlayerRemoteAddress = IPAddress.Parse(Input.
ReadString());
479 PlayerRemoteEndpoint =
new IPEndPoint(PlayerRemoteAddress, Input.
ReadUInt16());
483 if (!(Connection is
null))
484 await Connection.DisposeAsync();
494 bool AllConnected =
false;
495 bool DisposeConnection =
false;
496 PeerConnection ObsoleteConnection =
null;
498 lock (this.remotePlayersByEndpoint)
500 if (!this.playersById.TryGetValue(PlayerId, out Player))
501 DisposeConnection =
true;
504 if (Player.Connection is
null)
505 this.connectionCount++;
507 ObsoleteConnection = Player.Connection;
509 Player.Connection = Connection;
510 Connection.StateObject = Player;
511 Connection.RemoteEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
513 AllConnected = this.connectionCount + 1 == this.playerCount;
517 if (DisposeConnection)
519 if (!(Connection is
null))
520 await Connection.DisposeAsync();
525 if (!(ObsoleteConnection is
null))
526 await ObsoleteConnection.DisposeAsync();
538 Player = (Player)Connection.StateObject;
571 throw new Exception(
"The multiplayer environment is not ready to exchange data between players.");
577 await Connection.
SendTcp(Packet);
590 throw new Exception(
"The multiplayer environment is not ready to exchange data between players.");
593 return Connection?.
SendTcp(Packet) ?? Task.CompletedTask;
606 lock (this.remotePlayersByEndpoint)
608 if (!this.playersById.TryGetValue(PlayerId, out
Player))
609 throw new ArgumentException(
"No player with that ID.", nameof(PlayerId));
613 return Connection?.
SendTcp(Packet) ?? Task.CompletedTask;
623 public async Task
SendUdpToAll(
byte[] Packet,
int IncludeNrPreviousPackets)
626 throw new Exception(
"The multiplayer environment is not ready to exchange data between players.");
632 await Connection.
SendUdp(Packet, IncludeNrPreviousPackets);
647 throw new Exception(
"The multiplayer environment is not ready to exchange data between players.");
650 return Connection?.
SendUdp(Packet, IncludeNrPreviousPackets) ?? Task.CompletedTask;
661 public Task
SendUdpTo(Guid PlayerId,
byte[] Packet,
int IncludeNrPreviousPackets)
665 lock (this.remotePlayersByEndpoint)
667 if (!this.playersById.TryGetValue(PlayerId, out
Player))
668 throw new ArgumentException(
"No player with that ID.", nameof(PlayerId));
672 return Connection?.
SendUdp(Packet, IncludeNrPreviousPackets) ?? Task.CompletedTask;
675 private async Task Peer_OnClosed(
object Sender, EventArgs e)
685 lock (this.remotePlayersByEndpoint)
687 Player.Connection =
null;
688 this.connectionCount--;
690 Connection.StateObject =
null;
717 throw new Exception(
"The multiplayer environment is not in the state of finding players.");
725 this.localPlayer.Index = Index++;
726 this.Serialize(this.localPlayer, Output);
731 lock (this.remotePlayersByEndpoint)
733 Output.
WriteUInt((uint)this.remotePlayersByEndpoint.Count);
735 foreach (
Player Player in this.remotePlayersByEndpoint.Values)
737 Player.Index = Index++;
738 this.Serialize(
Player, Output);
746 this.mqttTerminatedPacketIdentifier = await this.mqttConnection.
PUBLISH(this.mqttNegotiationTopic,
MqttQualityOfService.AtLeastOnce,
false, Output);
747 this.mqttConnection.OnPublished += this.MqttConnection_OnPublished;
752 await this.StartConnecting();
755 private async Task StartConnecting()
760 if (this.remotePlayers.Length == 0)
773 Connection.StateObject =
Player;
774 Connection.OnClosed += this.Peer_OnClosed;
775 Connection.OnReceived += this.Connection_OnReceived;
789 private async Task<bool> Connection_OnReceived(
object Sender,
byte[] Buffer,
int Offset,
int Count)
791 PeerConnection Connection = (PeerConnection)Sender;
793 IPAddress PlayerRemoteAddress;
794 IPEndPoint PlayerRemoteEndpoint;
801 PlayerRemoteAddress = IPAddress.Parse(Input.
ReadString());
802 PlayerRemoteEndpoint =
new IPEndPoint(PlayerRemoteAddress, Input.
ReadUInt16());
806 await Connection.DisposeAsync();
810 Player Player = (Player)Connection.StateObject;
811 bool DisposeConnection =
false;
813 lock (this.remotePlayersByEndpoint)
815 if (!this.playersById.TryGetValue(PlayerId, out Player Player2) || Player2.PlayerId != Player.PlayerId)
816 DisposeConnection =
true;
818 Player.Connection = Connection;
821 if (DisposeConnection)
823 await Connection.DisposeAsync();
827 Connection.RemoteEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
829 Connection.OnReceived -= this.Connection_OnReceived;
830 Connection.OnReceived += this.Peer_OnReceived;
831 Connection.OnSent += this.Connection_OnSent;
835 Output.
WriteGuid(this.localPlayer.PlayerId);
839 await Connection.SendTcp(Output.
GetPacket());
841 await this.OnPlayerConnected.Raise(
this, Player);
846 private async Task<bool> Connection_OnSent(
object Sender,
byte[] Buffer,
int Offset,
int Count)
848 PeerConnection Connection = (PeerConnection)Sender;
849 Player Player = (Player)Connection.StateObject;
852 Connection.OnSent -=
this.Connection_OnSent;
854 bool DisposePlayerConnection =
false;
856 lock (this.remotePlayersByEndpoint)
858 if (Player.Connection == Connection)
859 this.connectionCount++;
861 DisposePlayerConnection =
true;
863 AllConnected = this.connectionCount + 1 == this.playerCount;
866 if (DisposePlayerConnection)
867 await Player.Connection.DisposeAsync();
881 private async Task MqttConnection_OnConnectionError(
object Sender,
Exception Exception)
894 if (this.state != NewState)
896 this.state = NewState;
966 return this.
Wait(10000);
974 public bool Wait(
int TimeoutMilliseconds)
976 switch (WaitHandle.WaitAny(
new WaitHandle[] { this.ready, this.error }, TimeoutMilliseconds))
990 [Obsolete(
"Use the DisposeAsync() method.")]
1008 await this.CloseMqtt();
1012 if (!(this.p2pNetwork is
null))
1015 this.p2pNetwork =
null;
1018 this.ready?.Dispose();
1021 this.error?.Dispose();
1024 if (!(this.remotePlayersByEndpoint is
null))
1028 lock (this.remotePlayersByEndpoint)
1030 this.playersById.Clear();
1031 this.remotePlayersByIndex.Clear();
1033 ToDispose =
new Player[this.remotePlayersByEndpoint.Count];
1034 this.remotePlayersByEndpoint.Values.CopyTo(ToDispose, 0);
1036 this.remotePlayersByEndpoint.Clear();
1037 this.remotePlayers =
null;
1048 private async Task CloseMqtt()
1050 if (!(this.mqttConnection is
null))
1057 Output.
WriteGuid(this.localPlayer.PlayerId);
1059 this.mqttTerminatedPacketIdentifier = await this.mqttConnection.
PUBLISH(this.mqttNegotiationTopic,
MqttQualityOfService.AtLeastOnce,
false, Output);
1060 this.mqttConnection.OnPublished += this.MqttConnection_OnPublished;
1069 this.mqttConnection =
null;
1074 private async Task MqttConnection_OnPublished(
object Sender, ushort PacketIdentifier)
1076 if (!(this.mqttConnection is
null) && PacketIdentifier == this.mqttTerminatedPacketIdentifier)
1079 this.mqttConnection =
null;
1093 get {
return this.localPlayer.Index == 0; }
Static class managing the application event log. Applications and services log events on this static ...
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.
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 serialize information into a a binary packet.
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.
void WriteUInt(ulong Value)
Writes a variable-length unsigned integer.
void WriteByte(byte Value)
Writes a byte to the binary output packet.
void WriteString(string Value)
Writes a string to the binary output packet.
Manages an MQTT connection. Implements MQTT v3.1.1, as defined in http://docs.oasis-open....
MqttState State
Current state of connection.
Task< ushort > PUBLISH(string Topic, MqttQualityOfService QoS, bool Retain, byte[] Data)
Publishes information on a topic.
Task< ushort > SUBSCRIBE(string Topic, MqttQualityOfService QoS)
Subscribes to information from a topic. Topics can include wildcards.
async Task DisposeAsync()
Closes the connection and disposes of all resources.
Information about content received from the MQTT server.
BinaryInput DataInput
Data stream that can be used to parse incoming data.
Event arguments for game data events.
IPAddress LocalAddress
Local IP Address.
Exception Exception
In case State=PeerToPeerNetworkState.Error, this exception object contains details about the error.
IPAddress ExternalAddress
External IP Address.
Manages a multi-player environment.
IPEndPoint ExternalEndpoint
External IP Endpoint.
string ApplicationName
Application Name
EventHandlerAsync< GameDataEventArgs > OnGameDataReceived
Event raised when game data has been received from a player.
IPAddress LocalAddress
Local IP Address.
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....
IPEndPoint LocalEndpoint
Local IP Endpoint.
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.
async Task DisposeAsync()
IDisposable.Dispose
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....
IPAddress ExternalAddress
External IP Address.
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.
async void Dispose()
IDisposable.Dispose
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.
int PlayerCount
Number of players
Maintains a peer connection
object StateObject
State object that applications can use to attach information to a connection.
void Start()
Starts receiving on the connection.
Task DisposeAsync()
IDisposable.Dispose
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
IPEndPoint LocalEndpoint
Local IP Endpoint.
Class containing information about a player.
override string ToString()
PeerConnection Connection
Peer connection, if any.
IPEndPoint PublicEndpoint
Public Endpoint
Event arguments for UDP Datagram events.
IPEndPoint RemoteEndpoint
Remote Endpoint
Serializes output to System.Console.Out, and assures modules are not dead-locked in case the Console ...
static void Write(string value)
Queues a value to be written to the console output.
static void WriteLine()
Queues a value to be written to the console output.
MqttQualityOfService
MQTT Quality of Service level.
MqttState
State of MQTT connection.
MultiPlayerState
State of multi-player environment.
PeerToPeerNetworkState
State of Peer-to-peer network.