6using System.Threading.Tasks;
41 RegisteringApplicationInGateway,
77 private ManualResetEvent ready =
new ManualResetEvent(
false);
78 private ManualResetEvent error =
new ManualResetEvent(
false);
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;
112 string MqttServer,
int MqttPort,
bool MqttTls,
string MqttUserName,
string MqttPassword,
113 string MqttNegotiationTopic,
int EstimatedMaxNrPlayers, Guid PlayerId, params KeyValuePair<string, string>[] PlayerMetaInfo)
115 this.localPlayer =
new Player(PlayerId,
new IPEndPoint(IPAddress.Any, 0),
new IPEndPoint(IPAddress.Any, 0), PlayerMetaInfo);
116 this.playersById[PlayerId] = this.localPlayer;
119 this.mqttServer = MqttServer;
120 this.mqttPort = MqttPort;
121 this.mqttTls = MqttTls;
122 this.mqttUserName = MqttUserName;
123 this.mqttPassword = MqttPassword;
124 this.mqttNegotiationTopic = MqttNegotiationTopic;
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;
137 lock (this.remotePlayersByEndpoint)
144 await Player.
Connection.UdpDatagramReceived(Sender, e);
170 this.exception =
null;
172 this.localPlayer.SetEndpoints(this.p2pNetwork.
ExternalEndpoint,
this.p2pNetwork.LocalEndpoint);
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;
190 this.exception = this.p2pNetwork.
Exception;
200 private async Task MqttConnection_OnStateChanged(
object Sender,
MqttState NewState)
204 await this.mqttConnection.
SUBSCRIBE(this.mqttNegotiationTopic);
210 this.localPlayer.SetEndpoints(this.p2pNetwork.
ExternalEndpoint,
this.p2pNetwork.LocalEndpoint);
211 this.Serialize(this.localPlayer, Output);
221 private void Serialize(Player Player,
BinaryOutput Output)
223 Output.
WriteString(Player.PublicEndpoint.Address.ToString());
224 Output.
WriteUInt16((ushort)Player.PublicEndpoint.Port);
226 Output.
WriteString(Player.LocalEndpoint.Address.ToString());
227 Output.
WriteUInt16((ushort)Player.LocalEndpoint.Port);
232 foreach (KeyValuePair<string, string> P
in Player)
241 IPAddress PublicAddress = IPAddress.Parse(Input.
ReadString());
243 IPEndPoint PublicEndpoint =
new IPEndPoint(PublicAddress, PublicPort);
250 bool LocalPlayer = PlayerId == this.localPlayer.PlayerId;
252 KeyValuePair<string, string>[] PlayerMetaInfo = LocalPlayer ? null :
new KeyValuePair<string, string>[c];
255 for (i = 0; i < c; i++)
260 PlayerMetaInfo[i] =
new KeyValuePair<string, string>(Key, Value);
266 return new Player(PlayerId, PublicEndpoint,
LocalEndpoint, PlayerMetaInfo);
269 private async Task MqttConnection_OnContentReceived(
object Sender,
MqttContent Content)
281 Player Player = this.Deserialize(Input);
288 IPEndPoint ExpectedEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
290 lock (this.remotePlayersByEndpoint)
292 this.remotePlayersByEndpoint[ExpectedEndpoint] = Player;
293 this.remotePlayerIPs[ExpectedEndpoint.Address] =
true;
294 this.playersById[Player.PlayerId] = Player;
296 this.UpdateRemotePlayersLocked();
307 Player = this.Deserialize(Input);
316 LinkedList<Player> Players =
new LinkedList<Player>();
317 bool LocalPlayerIncluded =
false;
319 Player.Index = Index++;
320 Players.AddLast(Player);
323 for (i = 0; i < c; i++)
325 Player = this.Deserialize(Input);
331 this.localPlayer.Index = Index++;
332 LocalPlayerIncluded =
true;
339 Player.Index = Index++;
340 Players.AddLast(Player);
347 if (!LocalPlayerIncluded)
351 this.mqttConnection =
null;
353 lock (this.remotePlayersByEndpoint)
355 this.remotePlayersByEndpoint.Clear();
356 this.remotePlayerIPs.Clear();
357 this.remotePlayersByIndex.Clear();
358 this.playersById.Clear();
360 this.remotePlayersByIndex[this.localPlayer.Index] = this.localPlayer;
361 this.playersById[this.localPlayer.PlayerId] = this.localPlayer;
363 foreach (Player Player2
in Players)
365 ExpectedEndpoint = Player2.GetExpectedEndpoint(this.p2pNetwork);
367 this.remotePlayersByIndex[Player2.Index] = Player2;
368 this.remotePlayersByEndpoint[ExpectedEndpoint] = Player2;
369 this.remotePlayerIPs[ExpectedEndpoint.Address] =
true;
370 this.playersById[Player2.PlayerId] = Player2;
373 this.UpdateRemotePlayersLocked();
377 await this.StartConnecting();
386 lock (this.remotePlayersByEndpoint)
388 if (!this.playersById.TryGetValue(PlayerId, out Player))
394 ExpectedEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
396 this.playersById.Remove(PlayerId);
397 this.remotePlayersByEndpoint.Remove(ExpectedEndpoint);
398 this.remotePlayersByIndex.Remove(Player.Index);
400 IPAddress ExpectedAddress = ExpectedEndpoint.Address;
401 bool AddressFound =
false;
403 foreach (IPEndPoint EP
in this.remotePlayersByEndpoint.Keys)
405 if (IPAddress.Equals(EP.Address, ExpectedAddress))
413 this.remotePlayerIPs.Remove(ExpectedAddress);
415 this.UpdateRemotePlayersLocked();
421 private void UpdateRemotePlayersLocked()
423 int c = this.remotePlayersByEndpoint.Count;
425 this.playerCount = 1 + c;
426 this.remotePlayers =
new Player[c];
427 this.remotePlayersByEndpoint.Values.CopyTo(this.remotePlayers, 0);
430 private async Task P2pNetwork_OnPeerConnected(
object Listener, PeerConnection Peer)
432 IPEndPoint Endpoint = (IPEndPoint)Peer.Tcp.Client.Client.RemoteEndPoint;
440 lock (this.remotePlayersByEndpoint)
442 if (!this.remotePlayerIPs.ContainsKey(Endpoint.Address))
448 await Peer.DisposeAsync();
452 Peer.OnClosed += this.Peer_OnClosed;
453 Peer.OnReceived += this.Peer_OnReceived;
457 Output.
WriteGuid(this.localPlayer.PlayerId);
461 await Peer.SendTcp(
true, Output.
GetPacket());
464 private async Task<bool> Peer_OnReceived(
object Sender,
bool ConstantBuffer,
byte[] Buffer,
int Offset,
int Count)
466 PeerConnection Connection = (PeerConnection)Sender;
470 if (Connection.StateObject is
null)
474 IPAddress PlayerRemoteAddress;
475 IPEndPoint PlayerRemoteEndpoint;
480 PlayerRemoteAddress = IPAddress.Parse(Input.
ReadString());
481 PlayerRemoteEndpoint =
new IPEndPoint(PlayerRemoteAddress, Input.
ReadUInt16());
485 if (!(Connection is
null))
486 await Connection.DisposeAsync();
496 bool AllConnected =
false;
497 bool DisposeConnection =
false;
498 PeerConnection ObsoleteConnection =
null;
500 lock (this.remotePlayersByEndpoint)
502 if (!this.playersById.TryGetValue(PlayerId, out Player))
503 DisposeConnection =
true;
506 if (Player.Connection is
null)
507 this.connectionCount++;
509 ObsoleteConnection = Player.Connection;
511 Player.Connection = Connection;
512 Connection.StateObject = Player;
513 Connection.RemoteEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
515 AllConnected = this.connectionCount + 1 == this.playerCount;
519 if (DisposeConnection)
521 if (!(Connection is
null))
522 await Connection.DisposeAsync();
527 if (!(ObsoleteConnection is
null))
528 await ObsoleteConnection.DisposeAsync();
540 Player = (Player)Connection.StateObject;
570 [Obsolete(
"Use an overload with a ConstantBuffer argument. This increases performance, as the buffer will not be unnecessarily cloned if queued.")]
586 throw new Exception(
"The multiplayer environment is not ready to exchange data between players.");
592 await Connection.
SendTcp(ConstantBuffer, Packet);
602 [Obsolete(
"Use an overload with a ConstantBuffer argument. This increases performance, as the buffer will not be unnecessarily cloned if queued.")]
605 return this.
SendTcpTo(Player,
false, Packet);
619 throw new Exception(
"The multiplayer environment is not ready to exchange data between players.");
622 return Connection?.
SendTcp(ConstantBuffer, Packet) ?? Task.CompletedTask;
631 [Obsolete(
"Use an overload with a ConstantBuffer argument. This increases performance, as the buffer will not be unnecessarily cloned if queued.")]
634 return this.
SendTcpTo(PlayerId,
false, Packet);
645 public Task
SendTcpTo(Guid PlayerId,
bool ConstantBuffer,
byte[] Packet)
649 lock (this.remotePlayersByEndpoint)
651 if (!this.playersById.TryGetValue(PlayerId, out
Player))
652 throw new ArgumentException(
"No player with that ID.", nameof(PlayerId));
656 return Connection?.
SendTcp(ConstantBuffer, Packet) ?? Task.CompletedTask;
666 public async Task
SendUdpToAll(
byte[] Packet,
int IncludeNrPreviousPackets)
669 throw new Exception(
"The multiplayer environment is not ready to exchange data between players.");
675 await Connection.
SendUdp(Packet, IncludeNrPreviousPackets);
690 throw new Exception(
"The multiplayer environment is not ready to exchange data between players.");
693 return Connection?.
SendUdp(Packet, IncludeNrPreviousPackets) ?? Task.CompletedTask;
704 public Task
SendUdpTo(Guid PlayerId,
byte[] Packet,
int IncludeNrPreviousPackets)
708 lock (this.remotePlayersByEndpoint)
710 if (!this.playersById.TryGetValue(PlayerId, out
Player))
711 throw new ArgumentException(
"No player with that ID.", nameof(PlayerId));
715 return Connection?.
SendUdp(Packet, IncludeNrPreviousPackets) ?? Task.CompletedTask;
718 private async Task Peer_OnClosed(
object Sender, EventArgs e)
728 lock (this.remotePlayersByEndpoint)
730 Player.Connection =
null;
731 this.connectionCount--;
733 Connection.StateObject =
null;
760 throw new Exception(
"The multiplayer environment is not in the state of finding players.");
768 this.localPlayer.Index = Index++;
769 this.Serialize(this.localPlayer, Output);
774 lock (this.remotePlayersByEndpoint)
776 Output.
WriteUInt((uint)this.remotePlayersByEndpoint.Count);
778 foreach (
Player Player in this.remotePlayersByEndpoint.Values)
780 Player.Index = Index++;
781 this.Serialize(
Player, Output);
789 this.mqttTerminatedPacketIdentifier = await this.mqttConnection.
PUBLISH(this.mqttNegotiationTopic,
MqttQualityOfService.AtLeastOnce,
false, Output);
790 this.mqttConnection.OnPublished += this.MqttConnection_OnPublished;
795 await this.StartConnecting();
798 private async Task StartConnecting()
803 if (this.remotePlayers.Length == 0)
816 Connection.StateObject =
Player;
817 Connection.OnClosed += this.Peer_OnClosed;
818 Connection.OnReceived += this.Connection_OnReceived;
832 private async Task<bool> Connection_OnReceived(
object Sender,
bool ConstantBuffer,
byte[] Buffer,
int Offset,
int Count)
834 PeerConnection Connection = (PeerConnection)Sender;
836 IPAddress PlayerRemoteAddress;
837 IPEndPoint PlayerRemoteEndpoint;
844 PlayerRemoteAddress = IPAddress.Parse(Input.
ReadString());
845 PlayerRemoteEndpoint =
new IPEndPoint(PlayerRemoteAddress, Input.
ReadUInt16());
849 await Connection.DisposeAsync();
853 Player Player = (Player)Connection.StateObject;
854 bool DisposeConnection =
false;
856 lock (this.remotePlayersByEndpoint)
858 if (!this.playersById.TryGetValue(PlayerId, out Player Player2) || Player2.PlayerId != Player.PlayerId)
859 DisposeConnection =
true;
861 Player.Connection = Connection;
864 if (DisposeConnection)
866 await Connection.DisposeAsync();
870 Connection.RemoteEndpoint = Player.GetExpectedEndpoint(this.p2pNetwork);
872 Connection.OnReceived -= this.Connection_OnReceived;
873 Connection.OnReceived += this.Peer_OnReceived;
874 Connection.OnSent += this.Connection_OnSent;
878 Output.
WriteGuid(this.localPlayer.PlayerId);
882 await Connection.SendTcp(
true, Output.
GetPacket());
884 await this.OnPlayerConnected.Raise(
this, Player);
889 private async Task<bool> Connection_OnSent(
object Sender,
bool ConstantBuffer,
byte[] Buffer,
int Offset,
int Count)
891 PeerConnection Connection = (PeerConnection)Sender;
892 Player Player = (Player)Connection.StateObject;
895 Connection.OnSent -=
this.Connection_OnSent;
897 bool DisposePlayerConnection =
false;
899 lock (this.remotePlayersByEndpoint)
901 if (Player.Connection == Connection)
902 this.connectionCount++;
904 DisposePlayerConnection =
true;
906 AllConnected = this.connectionCount + 1 == this.playerCount;
909 if (DisposePlayerConnection)
910 await Player.Connection.DisposeAsync();
924 private async Task MqttConnection_OnConnectionError(
object Sender,
Exception Exception)
937 if (this.state != NewState)
939 this.state = NewState;
1009 return this.
Wait(10000);
1017 public bool Wait(
int TimeoutMilliseconds)
1019 return WaitHandle.WaitAny(
new WaitHandle[] { this.ready, this.error }, TimeoutMilliseconds)
switch
1029 [Obsolete(
"Use the DisposeAsync() method.")]
1040 await this.CloseMqtt();
1044 if (!(this.p2pNetwork is
null))
1047 this.p2pNetwork =
null;
1050 this.ready?.Dispose();
1053 this.error?.Dispose();
1056 if (!(this.remotePlayersByEndpoint is
null))
1060 lock (this.remotePlayersByEndpoint)
1062 this.playersById.Clear();
1063 this.remotePlayersByIndex.Clear();
1065 ToDispose =
new Player[this.remotePlayersByEndpoint.Count];
1066 this.remotePlayersByEndpoint.Values.CopyTo(ToDispose, 0);
1068 this.remotePlayersByEndpoint.Clear();
1069 this.remotePlayers =
null;
1080 private async Task CloseMqtt()
1082 if (!(this.mqttConnection is
null))
1089 Output.
WriteGuid(this.localPlayer.PlayerId);
1091 this.mqttTerminatedPacketIdentifier = await this.mqttConnection.
PUBLISH(this.mqttNegotiationTopic,
MqttQualityOfService.AtLeastOnce,
false, Output);
1092 this.mqttConnection.OnPublished += this.MqttConnection_OnPublished;
1101 this.mqttConnection =
null;
1106 private async Task MqttConnection_OnPublished(
object Sender, ushort PacketIdentifier)
1108 if (!(this.mqttConnection is
null) && PacketIdentifier == this.mqttTerminatedPacketIdentifier)
1111 this.mqttConnection =
null;
1125 get {
return this.localPlayer.Index == 0; }
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.
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....
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.
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.
async Task SendTcpToAll(bool ConstantBuffer, byte[] Packet)
Sends a packet to all remote players using TCP. Can only be done if State=MultiPlayerState....
void Dispose()
IDisposable.Dispose
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.
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
Task SendTcpTo(Guid PlayerId, bool ConstantBuffer, byte[] Packet)
Sends a packet to a specific player using TCP. Can only be done if State=MultiPlayerState....
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 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
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
Abstract base class for sniffers. Implements default method overloads.
static byte[] CloneSection(byte[] Data, int Offset, int Count)
Clones a section of a byte array.
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.
Interface for asynchronously disposable objects.
MqttQualityOfService
MQTT Quality of Service level.
MqttState
State of MQTT connection.
MultiPlayerState
State of multi-player environment.
PeerToPeerNetworkState
State of Peer-to-peer network.