Merge pull request #1175 from aws-lumberyard-dev/mp_session_integ

Integrating AzFramework Session hooks to Multiplayer
This commit is contained in:
AMZN-puvvadar
2021-06-09 10:39:31 -07:00
committed by GitHub
10 changed files with 244 additions and 16 deletions
@@ -46,7 +46,7 @@ namespace AzFramework
};
//! ISessionHandlingClientRequests
//! The session handling events to invoke multiplayer component handle the work on client side
//! Requests made to the client to manage their membership in a session
class ISessionHandlingClientRequests
{
public:
@@ -63,14 +63,14 @@ namespace AzFramework
virtual void RequestPlayerLeaveSession() = 0;
};
//! ISessionHandlingServerRequests
//! The session handling events to invoke server provider handle the work on server side
class ISessionHandlingServerRequests
//! ISessionProviderRequests
//! Requests made to the service providing server/fleet management by the server
class ISessionHandlingProviderRequests
{
public:
AZ_RTTI(ISessionHandlingServerRequests, "{4F0C17BA-F470-4242-A8CB-EC7EA805257C}");
ISessionHandlingServerRequests() = default;
virtual ~ISessionHandlingServerRequests() = default;
AZ_RTTI(ISessionHandlingProviderRequests, "{4F0C17BA-F470-4242-A8CB-EC7EA805257C}");
ISessionHandlingProviderRequests() = default;
virtual ~ISessionHandlingProviderRequests() = default;
// Handle the destroy session process
virtual void HandleDestroySession() = 0;
@@ -84,9 +84,14 @@ namespace AzFramework
// @param playerConnectionConfig The required properties to handle the player leave session process
virtual void HandlePlayerLeaveSession(const PlayerConnectionConfig& playerConnectionConfig) = 0;
// Retrieves the file location of a pem-encoded TLS certificate
// Retrieves the file location of a pem-encoded TLS certificate for Client to Server communication
// @return If successful, returns the file location of TLS certificate file; if not successful, returns
// empty string.
virtual AZStd::string GetSessionCertificate() = 0;
virtual AZ::IO::Path GetExternalSessionCertificate() = 0;
// Retrieves the file location of a pem-encoded TLS certificate for Server to Server communication
// @return If successful, returns the file location of TLS certificate file; if not successful, returns
// empty string.
virtual AZ::IO::Path GetInternalSessionCertificate() = 0;
};
} // namespace AzFramework
@@ -45,6 +45,7 @@ namespace Multiplayer
AzNetworking::ByteBuffer<2048> m_userData;
};
using ClientDisconnectedEvent = AZ::Event<>;
using ConnectionAcquiredEvent = AZ::Event<MultiplayerAgentDatum>;
using SessionInitEvent = AZ::Event<AzNetworking::INetworkInterface*>;
using SessionShutdownEvent = AZ::Event<AzNetworking::INetworkInterface*>;
@@ -65,8 +66,12 @@ namespace Multiplayer
//! @param state The state of this connection
virtual void InitializeMultiplayer(MultiplayerAgentType state) = 0;
//! Adds a ClientDisconnectedEvent Handler which is invoked on the client when a disconnection occurs
//! @param handler The ClientDisconnectedEvent Handler to add
virtual void AddClientDisconnectedHandler(ClientDisconnectedEvent::Handler& handler) = 0;
//! Adds a ConnectionAcquiredEvent Handler which is invoked when a new endpoint connects to the session.
//! @param handler The SessionInitEvent Handler to add
//! @param handler The ConnectionAcquiredEvent Handler to add
virtual void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) = 0;
//! Adds a SessionInitEvent Handler which is invoked when a new network session starts.
@@ -9,6 +9,7 @@
<Packet Name="Connect" Desc="Client connection packet, on success the server will reply with an Accept">
<Member Type="uint16_t" Name="networkProtocolVersion" Init="0" />
<Member Type="Multiplayer::LongNetworkString" Name="ticket" />
</Packet>
<Packet Name="Accept" Desc="Server accept packet">
@@ -21,10 +21,12 @@ namespace Multiplayer
ClientToServerConnectionData::ClientToServerConnectionData
(
AzNetworking::IConnection* connection,
AzNetworking::IConnectionListener& connectionListener
AzNetworking::IConnectionListener& connectionListener,
const AZStd::string& providerTicket
)
: m_connection(connection)
, m_entityReplicationManager(*connection, connectionListener, EntityReplicationManager::Mode::LocalClientToRemoteServer)
, m_providerTicket(providerTicket)
{
m_entityReplicationManager.SetMaxRemoteEntitiesPendingCreationCount(cl_ClientMaxRemoteEntitiesPendingCreationCount);
m_entityReplicationManager.SetEntityPendingRemovalMs(cl_ClientEntityReplicatorPendingRemovalTimeMs);
@@ -24,7 +24,8 @@ namespace Multiplayer
ClientToServerConnectionData
(
AzNetworking::IConnection* connection,
AzNetworking::IConnectionListener& connectionListener
AzNetworking::IConnectionListener& connectionListener,
const AZStd::string& providerTicket = ""
);
~ClientToServerConnectionData() override;
@@ -38,8 +39,12 @@ namespace Multiplayer
void SetCanSendUpdates(bool canSendUpdates) override;
//! @}
const AZStd::string& GetProviderTicket() const;
void SetProviderTicket(const AZStd::string&);
private:
EntityReplicationManager m_entityReplicationManager;
AZStd::string m_providerTicket;
AzNetworking::IConnection* m_connection = nullptr;
bool m_canSendUpdates = true;
};
@@ -21,4 +21,14 @@ namespace Multiplayer
{
m_canSendUpdates = canSendUpdates;
}
inline const AZStd::string& ClientToServerConnectionData::GetProviderTicket() const
{
return m_providerTicket;
}
inline void ClientToServerConnectionData::SetProviderTicket(const AZStd::string& ticket)
{
m_providerTicket = ticket;
}
}
@@ -41,6 +41,8 @@ namespace Multiplayer
NetworkEntityHandle GetPrimaryPlayerEntity();
const NetworkEntityHandle& GetPrimaryPlayerEntity() const;
const AZStd::string& GetProviderTicket() const;
void SetProviderTicket(const AZStd::string&);
private:
void OnControlledEntityRemove();
@@ -51,6 +53,7 @@ namespace Multiplayer
NetworkEntityHandle m_controlledEntity;
EntityStopEvent::Handler m_controlledEntityRemovedHandler;
EntityServerMigrationEvent::Handler m_controlledEntityMigrationHandler;
AZStd::string m_providerTicket;
AzNetworking::IConnection* m_connection = nullptr;
bool m_canSendUpdates = false;
};
@@ -32,4 +32,14 @@ namespace Multiplayer
{
return m_controlledEntity;
}
inline const AZStd::string& ServerToClientConnectionData::GetProviderTicket() const
{
return m_providerTicket;
}
inline void ServerToClientConnectionData::SetProviderTicket(const AZStd::string& ticket)
{
m_providerTicket = ticket;
}
}
@@ -30,11 +30,12 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Components/CameraBus.h>
#include <AzFramework/Session/ISessionRequests.h>
#include <AzFramework/Session/SessionConfig.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzFramework/Visibility/IVisibilitySystem.h>
#include <AzFramework/Visibility/EntityBoundsUnionBus.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzNetworking/Framework/INetworking.h>
@@ -116,6 +117,31 @@ namespace Multiplayer
behaviorContext->Class<RpcIndex>();
behaviorContext->Class<ClientInputId>();
behaviorContext->Class<HostFrameId>();
behaviorContext->Class<MultiplayerSystemComponent>("MultiplayerSystemComponent")
->Attribute(AZ::Script::Attributes::Module, "multiplayer")
->Attribute(AZ::Script::Attributes::Category, "Multiplayer")
->Method("GetOnClientDisconnectedEvent", [](AZ::EntityId id) -> AZ::Event<>*
{
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(id);
if (!entity)
{
AZ_Warning("Multiplayer Property", false, "MultiplayerSystemComponent GetOnClientDisconnectedEvent failed. The entity with id %s doesn't exist, please provide a valid entity id.", id.ToString().c_str())
return nullptr;
}
MultiplayerSystemComponent* mpComponent = entity->FindComponent<MultiplayerSystemComponent>();
if (!mpComponent)
{
AZ_Warning("Multiplayer Property", false, "MultiplayerSystemComponent GetOnClientDisconnected failed. Entity '%s' (id: %s) is missing MultiplayerSystemComponent, be sure to add MultiplayerSystemComponent to this entity.", entity->GetName().c_str(), id.ToString().c_str())
return nullptr;
}
return &mpComponent->m_clientDisconnectedEvent;
})
->Attribute(
AZ::Script::Attributes::AzEventDescription,
AZ::BehaviorAzEventDescription{"On Client Disconnected Event"});
}
MultiplayerComponent::Reflect(context);
@@ -151,9 +177,11 @@ namespace Multiplayer
void MultiplayerSystemComponent::Activate()
{
AZ::TickBus::Handler::BusConnect();
AzFramework::SessionNotificationBus::Handler::BusConnect();
m_networkInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(AZ::Name(MPNetworkInterfaceName), sv_protocol, TrustZone::ExternalClientToServer, *this);
m_consoleCommandHandler.Connect(AZ::Interface<AZ::IConsole>::Get()->GetConsoleCommandInvokedEvent());
AZ::Interface<IMultiplayer>::Register(this);
AZ::Interface<AzFramework::ISessionHandlingClientRequests>::Register(this);
//! Register our gems multiplayer components to assign NetComponentIds
RegisterMultiplayerComponents();
@@ -161,10 +189,90 @@ namespace Multiplayer
void MultiplayerSystemComponent::Deactivate()
{
AZ::Interface<AzFramework::ISessionHandlingClientRequests>::Unregister(this);
AZ::Interface<IMultiplayer>::Unregister(this);
AzFramework::SessionNotificationBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
}
bool MultiplayerSystemComponent::RequestPlayerJoinSession(const AzFramework::SessionConnectionConfig& config)
{
AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(MultiplayerAgentType::Client);
AZStd::string hostname = config.m_dnsName.empty() ? config.m_ipAddress : config.m_dnsName;
const IpAddress ipAddress(hostname.c_str(), config.m_port, m_networkInterface->GetType());
ConnectionId connectionId = m_networkInterface->Connect(ipAddress);
AzNetworking::IConnection* connection = m_networkInterface->GetConnectionSet().GetConnection(connectionId);
if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so
{
connection->SetUserData(new ClientToServerConnectionData(connection, *this, config.m_playerSessionId));
}
else
{
reinterpret_cast<ClientToServerConnectionData*>(connection->GetUserData())->SetProviderTicket(config.m_playerSessionId);
}
return true;
}
void MultiplayerSystemComponent::RequestPlayerLeaveSession()
{
if (GetAgentType() == MultiplayerAgentType::Client)
{
AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(MultiplayerAgentType::Uninitialized);
auto visitor = [](IConnection& connection)
{
connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local);
};
m_networkInterface->GetConnectionSet().VisitConnections(visitor);
}
}
bool MultiplayerSystemComponent::OnSessionHealthCheck()
{
return true;
}
bool MultiplayerSystemComponent::OnCreateSessionBegin(const AzFramework::SessionConfig& sessionConfig)
{
// Check if session manager has a certificate for us and pass it along if so
if (AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get() != nullptr)
{
AZ::CVarFixedString externalCertPath = AZ::CVarFixedString(
AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get()->GetExternalSessionCertificate().c_str());
if (!externalCertPath.empty())
{
AZ::CVarFixedString commandString = "net_SslExternalCertificateFile " + externalCertPath;
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(commandString.c_str());
}
AZ::CVarFixedString internalCertPath = AZ::CVarFixedString(
AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get()->GetInternalSessionCertificate().c_str());
if (!internalCertPath.empty())
{
AZ::CVarFixedString commandString = "net_SslInternalCertificateFile " + internalCertPath;
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(commandString.c_str());
}
}
Multiplayer::MultiplayerAgentType serverType = sv_isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer;
AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(serverType);
return m_networkInterface->Listen(sessionConfig.m_port);
}
bool MultiplayerSystemComponent::OnDestroySessionBegin()
{
bool disconnectSuccessful = true;
IConnectionSet& connectionSet = m_networkInterface->GetConnectionSet();
connectionSet.VisitConnections([&disconnectSuccessful](IConnection& connection)
{
bool didDisconnect = connection.Disconnect(DisconnectReason::TerminatedByServer, TerminationEndpoint::Remote);
disconnectSuccessful = disconnectSuccessful && didDisconnect;
});
return disconnectSuccessful;
}
void MultiplayerSystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
const AZ::TimeMs deltaTimeMs = aznumeric_cast<AZ::TimeMs>(static_cast<int32_t>(deltaTime * 1000.0f));
@@ -306,6 +414,22 @@ namespace Multiplayer
[[maybe_unused]] MultiplayerPackets::Connect& packet
)
{
// Validate our session with the provider if any
if (AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get() != nullptr)
{
AzFramework::PlayerConnectionConfig config;
config.m_playerConnectionId = aznumeric_cast<uint32_t>(connection->GetConnectionId());
config.m_playerSessionId = packet.GetTicket();
if(!AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get()->ValidatePlayerJoinSession(config))
{
auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local); };
m_networkInterface->GetConnectionSet().VisitConnections(visitor);
return true;
}
reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->SetProviderTicket(packet.GetTicket().c_str());
}
if (connection->SendReliablePacket(MultiplayerPackets::Accept(InvalidHostId, sv_map)))
{
// Sync our console
@@ -463,7 +587,12 @@ namespace Multiplayer
if (connection->GetConnectionRole() == ConnectionRole::Connector)
{
AZLOG_INFO("New outgoing connection to remote address: %s", connection->GetRemoteAddress().GetString().c_str());
connection->SendReliablePacket(MultiplayerPackets::Connect(0));
AZ::CVarFixedString providerTicket;
if (connection->GetUserData() != nullptr)
{
providerTicket = reinterpret_cast<ClientToServerConnectionData*>(connection->GetUserData())->GetProviderTicket();
}
connection->SendReliablePacket(MultiplayerPackets::Connect(0, providerTicket));
}
else
{
@@ -517,11 +646,16 @@ namespace Multiplayer
AZStd::string reasonString = ToString(reason);
AZLOG_INFO("%s due to %s from remote address: %s", endpointString, reasonString.c_str(), connection->GetRemoteAddress().GetString().c_str());
// The authority is shutting down its connection
if (connection->GetConnectionRole() == ConnectionRole::Acceptor)
{
// The authority is shutting down its connection
m_shutdownEvent.Signal(m_networkInterface);
}
else if (GetAgentType() == MultiplayerAgentType::Client && connection->GetConnectionRole() == ConnectionRole::Connector)
{
// The client is disconnecting
m_clientDisconnectedEvent.Signal();
}
// Clean up any multiplayer connection data we've bound to this connection instance
if (connection->GetUserData() != nullptr)
@@ -530,6 +664,30 @@ namespace Multiplayer
delete connectionData;
connection->SetUserData(nullptr);
}
// Signal to session management that a user has left the server
if (m_agentType == MultiplayerAgentType::DedicatedServer || m_agentType == MultiplayerAgentType::ClientServer)
{
if (AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get() != nullptr &&
connection->GetConnectionRole() == ConnectionRole::Connector)
{
AzFramework::PlayerConnectionConfig config;
config.m_playerConnectionId = aznumeric_cast<uint32_t>(connection->GetConnectionId());
config.m_playerSessionId = reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->GetProviderTicket();
AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get()->HandlePlayerLeaveSession(config);
}
}
// Signal to session management when there are no remaining players in a dedicated server for potential cleanup
// We avoid this for client server as the host itself is a user
if (m_agentType == MultiplayerAgentType::DedicatedServer && connection->GetConnectionRole() == ConnectionRole::Connector)
{
if (AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get() != nullptr
&& m_networkInterface->GetConnectionSet().GetConnectionCount() == 0)
{
AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get()->HandleDestroySession();
}
}
}
MultiplayerAgentType MultiplayerSystemComponent::GetAgentType() const
@@ -566,6 +724,11 @@ namespace Multiplayer
AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType));
}
void MultiplayerSystemComponent::AddClientDisconnectedHandler(ClientDisconnectedEvent::Handler& handler)
{
handler.Connect(m_clientDisconnectedEvent);
}
void MultiplayerSystemComponent::AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler)
{
handler.Connect(m_connAcquiredEvent);
@@ -25,8 +25,15 @@
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/Threading/ThreadSafeDeque.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/Session/ISessionHandlingRequests.h>
#include <AzFramework/Session/SessionNotifications.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
namespace AzFramework
{
struct SessionConfig;
}
namespace AzNetworking
{
class INetworkInterface;
@@ -38,6 +45,8 @@ namespace Multiplayer
class MultiplayerSystemComponent final
: public AZ::Component
, public AZ::TickBus::Handler
, public AzFramework::SessionNotificationBus::Handler
, public AzFramework::ISessionHandlingClientRequests
, public AzNetworking::IConnectionListener
, public IMultiplayer
{
@@ -58,6 +67,13 @@ namespace Multiplayer
void Deactivate() override;
//! @}
//! AzFramework::SessionNotificationBus::Handler overrides.
//! @{
bool OnSessionHealthCheck() override;
bool OnCreateSessionBegin(const AzFramework::SessionConfig& sessionConfig) override;
bool OnDestroySessionBegin() override;
//! @}
//! AZ::TickBus::Handler overrides.
//! @{
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
@@ -82,10 +98,17 @@ namespace Multiplayer
void OnDisconnect(AzNetworking::IConnection* connection, AzNetworking::DisconnectReason reason, AzNetworking::TerminationEndpoint endpoint) override;
//! @}
//! ISessionHandlingClientRequests interface
//! @{
bool RequestPlayerJoinSession(const AzFramework::SessionConnectionConfig& sessionConnectionConfig) override;
void RequestPlayerLeaveSession() override;
//! @}
//! IMultiplayer interface
//! @{
MultiplayerAgentType GetAgentType() const override;
void InitializeMultiplayer(MultiplayerAgentType state) override;
void AddClientDisconnectedHandler(ClientDisconnectedEvent::Handler& handler) override;
void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override;
void AddSessionInitHandler(SessionInitEvent::Handler& handler) override;
void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override;
@@ -121,6 +144,7 @@ namespace Multiplayer
SessionInitEvent m_initEvent;
SessionShutdownEvent m_shutdownEvent;
ConnectionAcquiredEvent m_connAcquiredEvent;
ClientDisconnectedEvent m_clientDisconnectedEvent;
AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 };
HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId;