205 lines
8.1 KiB
C#
205 lines
8.1 KiB
C#
// This file is provided under The MIT License as part of RiptideSteamTransport.
|
|
// Copyright (c) Tom Weiland
|
|
// For additional information please see the included LICENSE.md file or view it on GitHub:
|
|
// https://github.com/tom-weiland/RiptideSteamTransport/blob/main/LICENSE.md
|
|
|
|
using Steamworks;
|
|
using System;
|
|
using System.Threading.Tasks;
|
|
using UnityEngine;
|
|
|
|
namespace Riptide.Transports.Steam
|
|
{
|
|
public class SteamClient : SteamPeer, IClient
|
|
{
|
|
public event EventHandler Connected;
|
|
public event EventHandler ConnectionFailed;
|
|
public event EventHandler<DataReceivedEventArgs> DataReceived;
|
|
public event EventHandler<DisconnectedEventArgs> Disconnected;
|
|
|
|
private const string LocalHostName = "localhost";
|
|
private const string LocalHostIP = "127.0.0.1";
|
|
|
|
private SteamConnection steamConnection;
|
|
private SteamServer localServer;
|
|
private Callback<SteamNetConnectionStatusChangedCallback_t> connectionStatusChanged;
|
|
|
|
public SteamClient(SteamServer localServer = null)
|
|
{
|
|
this.localServer = localServer;
|
|
}
|
|
|
|
public void ChangeLocalServer(SteamServer newLocalServer)
|
|
{
|
|
localServer = newLocalServer;
|
|
}
|
|
|
|
public bool Connect(string hostAddress, out Connection connection, out string connectError)
|
|
{
|
|
connection = null;
|
|
|
|
try
|
|
{
|
|
//SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
|
|
SteamNetworkingUtils.InitRelayNetworkAccess();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
connectError = $"Couldn't connect: {ex}";
|
|
return false;
|
|
}
|
|
|
|
connectError = $"Invalid host address '{hostAddress}'! Expected '{LocalHostIP}' or '{LocalHostName}' for local connections, or a valid Steam ID.";
|
|
if (hostAddress == LocalHostIP || hostAddress == LocalHostName)
|
|
{
|
|
if (localServer == null)
|
|
{
|
|
connectError = $"No locally running server was specified to connect to! Either pass a {nameof(SteamServer)} instance to your {nameof(SteamClient)}'s constructor or call its {nameof(SteamClient.ChangeLocalServer)} method before attempting to connect locally.";
|
|
connection = null;
|
|
return false;
|
|
}
|
|
|
|
connection = steamConnection = ConnectLocal();
|
|
return true;
|
|
}
|
|
else if (ulong.TryParse(hostAddress, out ulong hostId))
|
|
{
|
|
connection = steamConnection = TryConnect(new CSteamID(hostId));
|
|
return connection != null;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private SteamConnection ConnectLocal()
|
|
{
|
|
Debug.Log($"{LogName}: Connecting to locally running server...");
|
|
|
|
connectionStatusChanged = Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
|
|
CSteamID playerSteamId = SteamUser.GetSteamID();
|
|
|
|
SteamNetworkingIdentity clientIdentity = new SteamNetworkingIdentity();
|
|
clientIdentity.SetSteamID(playerSteamId);
|
|
SteamNetworkingIdentity serverIdentity = new SteamNetworkingIdentity();
|
|
serverIdentity.SetSteamID(playerSteamId);
|
|
|
|
SteamNetworkingSockets.CreateSocketPair(out HSteamNetConnection connectionToClient, out HSteamNetConnection connectionToServer, false, ref clientIdentity, ref serverIdentity);
|
|
|
|
localServer.Add(new SteamConnection(playerSteamId, connectionToClient, this));
|
|
OnConnected();
|
|
return new SteamConnection(playerSteamId, connectionToServer, this);
|
|
}
|
|
|
|
private SteamConnection TryConnect(CSteamID hostId)
|
|
{
|
|
try
|
|
{
|
|
connectionStatusChanged = Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
|
|
|
|
SteamNetworkingIdentity serverIdentity = new SteamNetworkingIdentity();
|
|
serverIdentity.SetSteamID(hostId);
|
|
|
|
SteamNetworkingConfigValue_t[] options = new SteamNetworkingConfigValue_t[] { };
|
|
HSteamNetConnection connectionToServer = SteamNetworkingSockets.ConnectP2P(ref serverIdentity, 0, options.Length, options);
|
|
|
|
ConnectTimeout();
|
|
return new SteamConnection(hostId, connectionToServer, this);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogException(ex);
|
|
OnConnectionFailed();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private async void ConnectTimeout() // TODO: confirm if this is needed, Riptide *should* take care of timing out the connection
|
|
{
|
|
Task timeOutTask = Task.Delay(6000); // TODO: use Riptide Client's TimeoutTime
|
|
await Task.WhenAny(timeOutTask);
|
|
|
|
if (!steamConnection.IsConnected)
|
|
OnConnectionFailed();
|
|
}
|
|
|
|
private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t callback)
|
|
{
|
|
if (!callback.m_hConn.Equals(steamConnection.SteamNetConnection))
|
|
{
|
|
// When connecting via local loopback connection to a locally running SteamServer (aka
|
|
// this player is also the host), other external clients that attempt to connect seem
|
|
// to trigger ConnectionStatusChanged callbacks for the locally connected client. Not
|
|
// 100% sure why this is the case, but returning out of the callback here when the
|
|
// connection doesn't match that between local client & server avoids the problem.
|
|
return;
|
|
}
|
|
|
|
switch (callback.m_info.m_eState)
|
|
{
|
|
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected:
|
|
OnConnected();
|
|
break;
|
|
|
|
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer:
|
|
SteamNetworkingSockets.CloseConnection(callback.m_hConn, 0, "Closed by peer", false);
|
|
OnDisconnected(DisconnectReason.Disconnected);
|
|
break;
|
|
|
|
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
|
|
SteamNetworkingSockets.CloseConnection(callback.m_hConn, 0, "Problem detected", false);
|
|
OnDisconnected(DisconnectReason.TransportError);
|
|
break;
|
|
|
|
default:
|
|
Debug.Log($"{LogName}: Connection state changed - {callback.m_info.m_eState} | {callback.m_info.m_szEndDebug}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void Poll()
|
|
{
|
|
if (steamConnection != null)
|
|
Receive(steamConnection);
|
|
}
|
|
|
|
// TODO: disable nagle so this isn't needed
|
|
//public void Flush()
|
|
//{
|
|
// foreach (SteamConnection connection in connections.Values)
|
|
// SteamNetworkingSockets.FlushMessagesOnConnection(connection.SteamNetConnection);
|
|
//}
|
|
|
|
public void Disconnect()
|
|
{
|
|
if (connectionStatusChanged != null)
|
|
{
|
|
connectionStatusChanged.Dispose();
|
|
connectionStatusChanged = null;
|
|
}
|
|
|
|
SteamNetworkingSockets.CloseConnection(steamConnection.SteamNetConnection, 0, "Disconnected", false);
|
|
steamConnection = null;
|
|
}
|
|
|
|
protected virtual void OnConnected()
|
|
{
|
|
Connected?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
|
|
protected virtual void OnConnectionFailed()
|
|
{
|
|
ConnectionFailed?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
|
|
protected override void OnDataReceived(byte[] dataBuffer, int amount, SteamConnection fromConnection)
|
|
{
|
|
DataReceived?.Invoke(this, new DataReceivedEventArgs(dataBuffer, amount, fromConnection));
|
|
}
|
|
|
|
protected virtual void OnDisconnected(DisconnectReason reason)
|
|
{
|
|
Disconnected?.Invoke(this, new DisconnectedEventArgs(steamConnection, reason));
|
|
}
|
|
}
|
|
}
|