Files
o3de/Code/Framework/GridMate/GridMate/Session/Session.h
T
2021-03-08 14:30:57 -08:00

1003 lines
45 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef GM_SESSION_H
#define GM_SESSION_H
#include <GridMate/GridMate.h>
#include <GridMate/Carrier/Handshake.h>
#include <GridMate/Replica/ReplicaMgr.h>
#include <AzCore/PlatformId/PlatformId.h>
#include <AzCore/State/HSM.h>
#include <GridMate/Containers/queue.h>
#include <GridMate/Containers/unordered_set.h>
#include <GridMate/GridMateService.h>
#include <GridMate/Serialize/UtilityMarshal.h>
namespace GridMate
{
class ReplicaManager;
struct PlayerId;
extern const EndianType kSessionEndian;
namespace Debug {
class SessionDriller;
}
typedef AZ::u32 MemberIDCompact;
/**
* MemberID interface class.
*/
struct MemberID
{
virtual ~MemberID() {}
virtual string ToString() const = 0;
virtual string ToAddress() const = 0;
virtual MemberIDCompact Compact() const = 0;
virtual bool IsValid() const = 0;
AZ_FORCE_INLINE bool operator==(const MemberID& rhs) const { return ToString() == rhs.ToString(); }
AZ_FORCE_INLINE bool operator!=(const MemberID& rhs) const { return ToString() != rhs.ToString(); }
AZ_FORCE_INLINE bool operator==(const MemberIDCompact& rhs) const { return Compact() == rhs; }
AZ_FORCE_INLINE bool operator!=(const MemberIDCompact& rhs) const { return Compact() != rhs; }
};
typedef string SessionID;
struct SearchInfo;
struct StatisticsData {};
struct GridSessionParam
{
enum ValueType
{
VT_INT32,
VT_INT64,
VT_FLOAT,
VT_DOUBLE,
VT_STRING,
VT_INT32_ARRAY,
VT_INT64_ARRAY,
VT_FLOAT_ARRAY,
VT_DOUBLE_ARRAY,
};
GridSessionParam()
: m_type(VT_STRING) {}
// Helper function to convert basic types to string and set the type.
void SetValue(AZ::s32 value);
void SetValue(AZ::s64 value);
void SetValue(float value);
void SetValue(double value);
void SetValue(const char* value) { m_type = VT_STRING; m_value = value; }
template<class Allocator>
void SetValue(const AZStd::basic_string<char, AZStd::char_traits<char>, Allocator>& value) { m_type = VT_STRING; m_value = value; }
void SetValue(AZ::s32* values, size_t numElements);
void SetValue(AZ::s64* values, size_t numElements);
void SetValue(float* values, size_t numElements);
void SetValue(double* values, size_t numElements);
string m_id;
string m_value;
AZ::u8 m_type;
AZ_FORCE_INLINE bool operator==(const GridSessionParam& rhs) const { return m_type == rhs.m_type && m_id == rhs.m_id && m_value == rhs.m_value; }
};
enum SessionTopology : AZ::u8
{
/// Invalid topology, waiting for the host to deliver it's topology data.
ST_INVALID,
/// Standard client server. When a user joins he will be connected to the server only.
ST_CLIENT_SERVER,
/// Standard peer to peer. When a user joins he will connect to all other users.
ST_PEER_TO_PEER,
/// Client server mode, where client can have the replica/data ownership.
ST_CLIENT_SERVER_DISTRIBUTED,
};
struct SessionParams
{
enum Flags
{
SF_HOST_MIGRATION = (1 << 0), ///< Enable/disable host migration for this session. (default: Enabled). It will always migrate the session even if you loose all connections.
SF_HOST_MIGRATION_NO_EMPTY_SESSIONS = (1 << 1), ///< Same as \ref SF_HOST_MIGRATION except it will NOT create sessions with 1 member, it will just leave the session.
};
SessionParams()
: m_topology(ST_PEER_TO_PEER)
, m_peerToPeerTimeout(10000)
, m_hostMigrationTimeout(10000)
, m_hostMigrationVotingTime(2000)
, m_numPublicSlots(0)
, m_numPrivateSlots(0)
, m_flags(SF_HOST_MIGRATION)
, m_numParams(0) {}
SessionTopology m_topology;
unsigned int m_peerToPeerTimeout; ///< Peer to peer connectivity timeout in milliseconds. Recommended 2x the handshake time.
unsigned int m_hostMigrationTimeout; ///< Timeout for a host migration procedure in milliseconds. If it takes more time we will leave the session.
unsigned int m_hostMigrationVotingTime; ///< Minimum time that will spend voting (unless everybody voted - we have all the results) before we check the winner. IMPORTANT: value is clamped to less than 1/2 of m_hostMigrationTimeout.
unsigned int m_numPublicSlots; ///< Number of slots for players this session will have.
unsigned int m_numPrivateSlots;
unsigned char m_flags;
unsigned int m_numParams;
static const unsigned int k_maxNumParams = 32;
GridSessionParam m_params[k_maxNumParams]; ///< An array of session params.
};
struct JoinParams
{
JoinParams()
: m_desiredPeerMode(Mode_Undefined)
{}
RemotePeerMode m_desiredPeerMode;
};
enum class GridSessionSearchOperators : AZ::u8
{
SSO_OPERATOR_EQUAL = 1, ///< (==)
SSO_OPERATOR_NOT_EQUAL, ///< (!=)
SSO_OPERATOR_LESS_THAN, ///< (<)
SSO_OPERATOR_LESS_EQUAL_THAN, ///< (<=)
SSO_OPERATOR_GREATER_THAN, ///< (>)
SSO_OPERATOR_GREATER_EQUAL_THAN,///< (>=)
};
struct GridSessionSearchParam
: public GridSessionParam
{
GridSessionSearchOperators m_op; ///< Operator \ref GridSessionSearchOperators
};
struct SearchParams
{
SearchParams()
: m_maxSessions(s_defaultMaxSessions)
, m_timeOutMs(2000)
, m_numParams(0)
, m_version(1)
{}
static const unsigned int s_defaultMaxSessions = 8; ///< default limit for returned session entries, user can override this by setting m_maxSessions
unsigned int m_maxSessions; ///< maximum number of session entries to return from search
unsigned int m_timeOutMs;
AZ::u32 m_numParams; ///< number of parameters set in m_params
GridSessionSearchParam m_params[SessionParams::k_maxNumParams]; ///< A list of params for a search (matchmaking).
VersionType m_version;
};
struct SearchInfo
{
SearchInfo()
: m_numFreePublicSlots(0)
, m_numFreePrivateSlots(0)
, m_numUsedPublicSlots(0)
, m_numUsedPrivateSlots(0)
, m_numPlayers(0)
, m_numParams(0)
, m_port(0)
{ }
SessionID m_sessionId;
AZ::u32 m_numFreePublicSlots;
AZ::u32 m_numFreePrivateSlots;
AZ::u32 m_numUsedPublicSlots;
AZ::u32 m_numUsedPrivateSlots;
AZ::u32 m_numPlayers; ///< Number of players in the session.
AZ::u32 m_numParams;
AZ::u32 m_port;
GridSessionParam m_params[SessionParams::k_maxNumParams];
};
struct SessionIdInfo
{
SessionID m_sessionId;
};
class GridMember;
class GridSession;
class GridSearch;
class IGridMate;
/**
* Grid Session Callbacks
*/
class GridSessionCallbacks
: public GridMateEBusTraits
{
public:
virtual ~GridSessionCallbacks() {}
/// Callback that is called when the Session service is ready to process sessions.
virtual void OnSessionServiceReady() {}
//virtual OnCommucationChanged() = 0 Callback that notifies the title when a member's communication settings change.
/// Callback when we start a grid search.
virtual void OnGridSearchStart(GridSearch* gridSearch) { (void)gridSearch; }
/// Callback that notifies the title when a game search query have completed.
virtual void OnGridSearchComplete(GridSearch* gridSearch) { (void)gridSearch; }
/// Callback when we release (delete) a grid search. It's not safe to hold the grid pointer after this.
virtual void OnGridSearchRelease(GridSearch* gridSearch) { (void)gridSearch; }
/// Callback that notifies the title when a new member joins the game session.
virtual void OnMemberJoined(GridSession* session, GridMember* member) { (void)session; (void)member; }
/// Callback that notifies the title that a member is leaving the game session. member pointer is NOT valid after the callback returns.
virtual void OnMemberLeaving(GridSession* session, GridMember* member) { (void)session; (void)member; }
// \todo a better way will be (after we solve migration) is to supply a reason to OnMemberLeaving... like the member was kicked.
// this will require that we actually remove the replica at the same moment.
/// Callback that host decided to kick a member. You will receive a OnMemberLeaving when the actual member leaves the session.
virtual void OnMemberKicked(GridSession* session, GridMember* member, AZ::u8 reason) { (void)session; (void)member; (void)reason; }
/// Called when new session is created. Client session might not sync yet at this point.
virtual void OnSessionCreated(GridSession* session) { (void)session; }
/// After this callback it is safe to access session features. Host session is fully operational
virtual void OnSessionHosted(GridSession* session) { (void)session; }
/// After this callback it is safe to access session features. Client session is fully operational
virtual void OnSessionJoined(GridSession* session) { (void)session; }
/// Callback that notifies the title when a session will be left. session pointer is NOT valid after the callback returns.
virtual void OnSessionDelete(GridSession* session) { (void)session; }
/// Called when a session error occurs.
virtual void OnSessionError(GridSession* session, const string& errorMsg) { (void)session; (void)errorMsg; }
/// Called when the actual game(match) starts
virtual void OnSessionStart(GridSession* session) { (void)session; }
/// Called when the actual game(match) ends
virtual void OnSessionEnd(GridSession* session) { (void)session; }
/// Called when we start a host migration.
virtual void OnMigrationStart(GridSession* session) { (void)session; }
/// Called so the user can select a member that should be the new Host. Value will be ignored if NULL, current host or the member has invalid connection id.
virtual void OnMigrationElectHost(GridSession* session, GridMember*& newHost) { (void)session; (void)newHost; }
/// Called when the host migration has completed.
virtual void OnMigrationEnd(GridSession* session, GridMember* newHost) { (void)session; (void)newHost; }
/// Called when we have our last chance to write statistics data for member in the session.
virtual void OnWriteStatistics(GridSession* session, GridMember* member, StatisticsData& data) { (void)session; (void)member; (void)data; }
};
typedef AZ::EBus<GridSessionCallbacks> SessionEventBus;
/**
* SessionServiceDesc descriptor.
* This struct is derived from for the different platforms.
*/
struct SessionServiceDesc
{
};
namespace Internal
{
class GridSessionReplica;
class GridSessionHandshake;
class GridMemberStateReplica;
}
/**
* Grid member interface class.
*/
class GridMember
: public ReplicaChunk
{
friend class GridSession;
friend class SessionService;
friend class Internal::GridMemberStateReplica;
public:
GM_CLASS_ALLOCATOR(GridMember);
virtual ~GridMember() {}
/// return an abstracted member id. (member ID is world unique but unrelated to player ID it's related to the session).
virtual const MemberID& GetId() const = 0;
/// return a compact version of the member id.
const MemberIDCompact& GetIdCompact() const { return m_memberIdCompact; }
/// Returns a player ID that's unique in the world and it's not session related. If NULL not player ID is supported.
virtual const PlayerId* GetPlayerId() const = 0;
NatType GetNatType() const;
string GetName() const;
GridSession* GetSession() const { return m_session; }
bool IsHost() const { return m_isHost.Get(); }
bool IsLocal() const;
bool IsInvited() const { return m_isInvited.Get(); }
const RemotePeerMode GetPeerMode() const { return m_peerMode.Get(); }
/**
* Returns true if all member related data is present. Even if IsReady is false you can still access
* all functions! Unless explicitly specified otherwise.
*/
bool IsReady() const { return m_clientState; }
///< Mutes (audio/video send or received) from this member. Can be called on your local members.
void Mute(GridMember* member)
{
if (member && member != this)
{
Mute(member->GetIdCompact());
}
}
void Mute(const MemberIDCompact& id);
///< Unmute (audio/video is send and received) from this member. Can be called on your local members.
void Unmute(GridMember* member)
{
if (member && member != this)
{
Unmute(member->GetIdCompact());
}
}
void Unmute(const MemberIDCompact& id);
///< Check if a member is muted by this member.
bool IsMuted(GridMember* member) const { return (member && member != this) ? IsMuted(member->GetIdCompact()) : true; }
bool IsMuted(const MemberIDCompact& id) const;
///< Return true if the member is talking on the microphone.
bool IsTalking() const;
///< Refresh talking state for given member, member will be marked as talking
void UpdateTalking();
/// @{ Binary data exchange - \ref Carrier for more detailed info about send and receive. Send can fail if current connectionId is invalid!
bool SendBinary(const void* data, unsigned int dataSize, Carrier::DataReliability reliability = Carrier::SEND_RELIABLE, Carrier::DataPriority priority = Carrier::PRIORITY_NORMAL);
Carrier::ReceiveResult ReceiveBinary(char* data, unsigned int maxDataSize);
/// @}
ConnectionID GetConnectionId() const { return m_connectionId; }
//@{ Platform information
AZ::PlatformID GetPlatformId() const;
AZ::u32 GetProcessId() const;
string GetMachineName() const;
//@}
protected:
GridMember(const MemberIDCompact& memberIdCompact);
//////////////////////////////////////////////////////////////////////////
// Replica
bool IsReplicaMigratable() override;
bool IsBroadcast() override { return true; }
void OnReplicaActivate(const ReplicaContext& rc) override;
void OnReplicaDeactivate(const ReplicaContext& rc) override;
void OnReplicaChangeOwnership(const ReplicaContext& rc) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// RPC
bool OnKick(AZ::u8 reason, const RpcContext& rc);
//////////////////////////////////////////////////////////////////////////
void SetHost(bool isHost);
void SetInvited(bool isInvited);
ReplicaPtr m_clientStateReplica; ///< The state is a replica owned by the actual member, while the GridMember is managed by the session!
AZStd::intrusive_ptr<Internal::GridMemberStateReplica> m_clientState; ///< The state is a replica owned by the actual member, while the GridMember is managed by the session!
ConnectionID m_connectionId;
GridSession* m_session;
int m_slotType; ///< Local slot type returned from GridSession::ReserveSlot.
TimeStamp m_voiceDataProcessed; ///< Time stamp when we last processed input/output voice data for this member.
MemberIDCompact m_memberIdCompact; ///< Compact ID version (faster transfer etc.) if the full \ref MemberID Member ID
DataSet<bool> m_isHost;
DataSet<bool> m_isInvited;
DataSet<RemotePeerMode> m_peerMode; // topology used by the client
Rpc<RpcArg<AZ::u8>>::BindInterface<GridMember, &GridMember::OnKick> KickRpc;
};
/**
* Session interface class.
*/
class GridSession
: public CarrierEventBus::Handler
, public ReplicaMgrCallbackBus::Handler
{
typedef unsigned int SessionTime;
friend class GridMember;
friend class Internal::GridSessionReplica;
friend class Internal::GridMemberStateReplica;
friend class SessionService;
friend class Debug::SessionDriller;
public:
enum CarrierChannels
{
CC_REPLICA_DATA,
CC_VOICE_DATA,
CC_USER_DATA,
};
enum Result
{
GS_OK = 0,
GS_ERROR
};
virtual void Update();
/// Displays send invite dialog and binds current session data to it.
virtual bool SendInviteDlg() { return false; }
const SessionID& GetId() const { return m_sessionId; }
unsigned int GetNumberOfMembers() const { return static_cast<unsigned int>(m_members.size()); }
GridMember* GetMemberByIndex(unsigned int index) const
{
if(m_members.size() <= index)
{
return nullptr;
}
return m_members[index];
}
GridMember* GetMemberById(const MemberID& id) const;
GridMember* GetMemberById(const MemberIDCompact& id) const;
GridMember* GetHost() const;
GridMember* GetMyMember() const { return m_myMember; }
Result KickMember(GridMember* member, AZ::u8 reason = 0);
Result BanMember(GridMember* member, AZ::u8 reason = 0);
// not supported yet
virtual Result LockSession() { return GS_ERROR; }
virtual Result UnlockSession() { return GS_ERROR; }
//??? Migration
/**
* Leaves the session. If the local system is the host, isMigrateHost will
* determine the host migration. If the local system is NOT the host, it will just leave the session.
* \note When you call this you need to make sure you session supports host migration. The code is allowed to assert
* if such operation is NOT supported.
* IMPORTANT: You can't use the session pointer after this call.
*/
void Leave(bool isMigrateHost);
//virtual bool IsGameSession() const = 0;
bool IsHost() const { return m_myMember ? m_myMember->IsHost() : false; }
bool IsReady() const { return m_sm.IsInState(SS_IN_SESSION); }
/// Return true if we are in host migration state, false otherwise.
bool IsMigratingHost() const { return m_hostMigrationInProcess; }
/// returns time in milliseconds since the session started.
unsigned int GetTime() const;
SessionTopology GetTopology() const;
unsigned char GetNumUsedPublicSlots() const;
unsigned char GetNumUsedPrivateSlots() const;
unsigned char GetNumFreePublicSlots() const;
unsigned char GetNumFreePrivateSlots() const;
unsigned char GetFlags() const;
/// Returns timeout in milliseconds for the host to tolerate 2 peers with different number of connections. The peer with less connections will be kicked out after that timeout expires.
unsigned int GetPeerToPeerTimeout() const;
/// Return host migration max time in milliseconds. If timeout expires the used will leave the current session.
unsigned int GetHostMigrationTimeout() const;
/// Return host migration min voting time. Value between 0 and GetHostMigrationTimeout() / 2 for voting time, before we go to elect the winner.
unsigned int GetHostMigrationVotingTime() const;
// Returns the number of session parameters currently set
unsigned int GetNumParams() const;
// Returns session parameter by index
const GridSessionParam& GetParam(unsigned int index) const;
// Adds/updates a parameter. Returns false if parameter can not be added.
bool SetParam(const GridSessionParam& param);
// Removes a parameter by id. Returns false if parameter can not be removed.
bool RemoveParam(const string& paramId);
// Removes a parameter by index. Returns false if parameter can not be removed.
bool RemoveParam(unsigned int index);
/// Returns the instance of the replica manager. Replica manager may not be initialized if the session is NOT in ready (created) state.
ReplicaManager* GetReplicaMgr() { return m_replicaMgr; }
/// Returns the pointer to the transport layer. The pointer can be NULL if the session is NOT in ready (created) state.
Carrier* GetCarrier() { return m_carrier; }
/// Returns pointer to the descriptor which was used to create the transport layer (carrier)
const CarrierDesc& GetCarrierDesc() const { return m_carrierDesc; }
/// Return owner gridmate
IGridMate* GetGridMate() { return m_gridMate; }
/// @{ Debug: Change the disconnect detection state of all members in the session
void DebugEnableDisconnectDetection(bool isEnable);
bool DebugIsEnableDisconnectDetection() const;
/// @}
protected:
// Currently we support ONLY one carrier per session, since when we received messages from the carrier we don't prefix them with session ID
// we we do so, we can share a carrier between sessions. TODO: If you enable it make sure to add function to check if you own that carrier or NOT
// and assert if the user call GetCarrierDesc() since the returned descriptor will NOT be the one we use to create the carrier.
explicit GridSession(SessionService* service);
virtual ~GridSession();
/// Base initialization, must be called before all other operations on session
bool Initialize(const CarrierDesc& carrierDesc);
/// Called by the system to shutdown all session resources, before it's get deleted
virtual void Shutdown();
/**
* Sets the GridSessionHandshake user data, which will be delivered with each connection request
* and passed as a ReadBuffer into CreateRemoteMember.
*/
void SetHandshakeUserData(const void* data, size_t size);
//////////////////////////////////////////////////////////////////////////
// CarrierEventBus
void OnIncomingConnection(Carrier* carrier, ConnectionID id) override;
void OnFailedToConnect(Carrier* carrier, ConnectionID id, CarrierDisconnectReason reason) override;
void OnConnectionEstablished(Carrier* carrier, ConnectionID id) override;
void OnDisconnect(Carrier* carrier, ConnectionID id, CarrierDisconnectReason reason) override;
void OnDriverError(Carrier* carrier, ConnectionID id, const DriverError& error) override;
void OnSecurityError(Carrier* carrier, ConnectionID id, const SecurityError& error) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Replica Manager
void OnNewHost(bool isHost, ReplicaManager* manager) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/// Return slot type 1 for public slot, 2 private slot and 0 for no slot.
int ReserveSlot(bool isInvited);
/// Frees a slot based on a slot type.
void FreeSlot(int slotType);
/// Creates remote player, when he wants to join.
virtual GridMember* CreateRemoteMember(const string& address, ReadBuffer& data, RemotePeerMode peerMode, ConnectionID connId = InvalidConnectionID) = 0;
/// Returns true if this address belongs to a member in the list, otherwise false.
virtual bool IsAddressInMemberList(const string& address);
virtual bool IsConnectionIdInMemberList(const ConnectionID& connId);
/// Adds a created member to the session. Return false if no free slow was found!
virtual bool AddMember(GridMember* member);
/// Remove and delete current member from the session.
virtual bool RemoveMember(const MemberID& id);
/// Called by the state machine to elect a new host.
virtual void ElectNewHost();
/// Called by default from ElectNewHost to cast the new host vote.
void CastNewHostVote(GridMember* newHost);
/// Called when a session parameter is added/changed.
virtual void OnSessionParamChanged(const GridSessionParam& param) = 0;
/// Called when a session parameter is deleted.
virtual void OnSessionParamRemoved(const string& paramId) = 0;
//////////////////////////////////////////////////////////////////////////
SessionID m_sessionId; ///< Session id. Content of the string will vary based on session types and platforms.
CarrierDesc m_carrierDesc;
Carrier* m_carrier;
ReplicaManager* m_replicaMgr;
Internal::GridSessionHandshake* m_handshake;
typedef unordered_set<ConnectionID> ConnectionIDSet;
ConnectionIDSet m_connections;
string m_hostAddress;
bool m_isShutdown;
GridMember* m_myMember; ///< Created with the session and bound when the server replica arrives.
AZStd::intrusive_ptr<Internal::GridSessionReplica> m_state; ///< Pointer to HOST owned session state.
SessionService* m_service; ///< Pointer to session service.
IGridMate* m_gridMate; ///< Pointer to the owner GridMate interface.
typedef vector<GridMember*> MemberArrayType;
MemberArrayType m_members; ///< List of valid members.
unordered_set<Internal::GridMemberStateReplica*> m_unboundMemberStates; // Holds member state replicas until the member replica arrives.
// They should not be in here for very long.
TimeStamp m_hostMigrationStart; ///< Time when the host migration started.
unsigned int m_hostMigrationTimeOut; ///< Host migration time out in milliseconds
unsigned int m_hostMigrationVotingTime; ///< Minimum time that we will spend in SS_HOST_MIGRATE_ELECTION state (before we check for majority). It should be < than m_hostMigrationTimeOut/2
/** \note We use a bool instead of m_sm.IsInState(SS_HOST_MIGRATE_ELECTION)||m_sm.IsInState(SS_HOST_MIGRATE_SESSION)
* because if the migration failles or timeout it will switch states. This will cause inconsistency with the user callbacks.
*/
bool m_hostMigrationInProcess; ///< True if we are in host migration, otherwise false.
bool m_hostMigrationSessionMigrated;
bool m_hostMigrationReplicaMigrated;
/**
* In peer to peer network the host controls the connection mesh.
* If a member has less connections than a host means that he can not connect to certain peers.
* In peer to peer mode this is not allowed, this member will be added to "not fully connected" list
* and observed if he corrects (connects) it's state. If not after a certain time limit he will be kicked out.
* \note Host is the only one that can control and use that list.
*/
struct NotFullyConnectedMember
{
NotFullyConnectedMember(GridMember* m)
: member(m) {}
bool operator==(const NotFullyConnectedMember& rhs) { return member == rhs.member; }
TimeStamp time; ///< Time since the not connected state started.
GridMember* member;
};
typedef vector<NotFullyConnectedMember> NotConnectedArrayType;
NotConnectedArrayType m_membersNotFullyConnected;
/*SessionTime*/TimeStamp m_lastConnectivityUpdate;
//////////////////////////////////////////////////////////////////////////
// State machine
// valid session states
enum BaseStates
{
SS_TOP,
SS_NO_SESSION,
SS_START_UP,
SS_CREATE,
SS_IN_SESSION,
SS_JOIN,
SS_IDLE,
SS_START_GAME,
SS_IN_GAME,
SS_END_GAME,
SS_DELETE,
SS_HOST_MIGRATE_ELECTION,
SS_HOST_MIGRATE_SESSION,
SS_LAST, /// Must be last
};
enum BaseStateEvents
{
SE_UPDATE, ///< Called every frame to update a state.
SE_HOST,
SE_JOIN,
SE_JOIN_INVITE,
SE_JOINED,
SE_CREATED,
SE_START_GAME,
SE_LEAVE_GAME,
SE_END_GAME,
SE_DELETE,
SE_DELETED,
SE_CONNECTION_LOST,
// Host Migration
SE_HM_SESSION_MIGRATED, ///< Event executed on the host, when the session is migrated.
SE_HM_MIGRATE_CLIENT, ///< Event executed on the client, when he must join a migrated session.
SE_HM_CLIENT_SESSION_MIGRATED, ///< Event executes on the client, when the client joined the migrated session.
SE_HM_REPLICAS_MIGRATED, ///< Called when all replicas have been successfully migrated
SE_LAST, /// Must be last
};
virtual void SetUpStateMachine();
virtual bool OnStateNoSession(AZ::HSM& sm, const AZ::HSM::Event& e);
virtual bool OnStateStartup(AZ::HSM& sm, const AZ::HSM::Event& e);
virtual bool OnStateCreate(AZ::HSM& sm, const AZ::HSM::Event& e);
virtual bool OnStateInSession(AZ::HSM& sm, const AZ::HSM::Event& e);
virtual bool OnStateJoin(AZ::HSM& sm, const AZ::HSM::Event& e);
virtual bool OnStateIdle(AZ::HSM& sm, const AZ::HSM::Event& e);
virtual bool OnStateStart(AZ::HSM& sm, const AZ::HSM::Event& e);
virtual bool OnStateInGame(AZ::HSM& sm, const AZ::HSM::Event& e);
virtual bool OnStateEnd(AZ::HSM& sm, const AZ::HSM::Event& e);
virtual bool OnStateDelete(AZ::HSM& sm, const AZ::HSM::Event& e);
virtual bool OnStateHostMigrateElection(AZ::HSM& sm, const AZ::HSM::Event& e);
virtual bool OnStateHostMigrateSession(AZ::HSM& sm, const AZ::HSM::Event& e);
struct EventCommand
{
typedef void* (* DataCloner)(const void* /*sourceData*/, unsigned int /*sourceDataSize*/);
typedef void (* DataDeleter)(void* /*data*/, unsigned int /*dataSize*/);
AZ::HSM::Event event;
DataDeleter m_dataDeleter;
unsigned int m_userDataSize;
bool m_isProcessRequired;
};
/**
* Request a state machine event. You might override and queue the events if you have async operations.
* if isProcessRequired is set to true, the event dispatch will verify that a state function returned true (processed the event)
* otherwise it will trigger an assert and dispatch(SE_DELETE) event.
* \note we can add optional completion callbacks if needed.
* \note Important function function is to be used with POD data by default, provide DataCloner and DataDestructor for non POD.
*/
virtual void RequestEvent(int id, const void* userData, unsigned int userDataSize, bool isProcessRequired, EventCommand::DataCloner cloner = nullptr, EventCommand::DataDeleter deleter = nullptr);
/// If you queue events, you can override this function to process them when appropriate.
virtual void ProcessEvents();
template<class T>
static void* UserDataCopier(const void* sourceData, unsigned int sourceDataSize)
{
(void)sourceDataSize;
AZ_Assert(sizeof(T) == sourceDataSize, "Data size %d doesn't match the type size %d", sourceDataSize, sizeof(T))
return azcreate(T, (*static_cast<const T*>(sourceData)), GridMateAllocatorMP, "UserDataCopier");
}
template<class T>
static void UserDataDeleter(void* pointer, unsigned int dataSize)
{
(void)dataSize;
AZ_Assert(sizeof(T) == dataSize, "Data size %d doesn't match the type size %d", dataSize, sizeof(T));
T* data = static_cast<T*>(pointer);
azdestroy(data, GridMateAllocatorMP, T);
}
/// T must be copy constructible.
template<class T>
inline void RequestEventData(int id, const T& userData, bool isProcessRequired = false)
{
RequestEvent(id, &userData, sizeof(T), isProcessRequired, &UserDataCopier<T>, &UserDataDeleter<T>);
}
inline void RequestEventParam(int id, size_t param, bool isProcessRequired = false)
{
RequestEvent(id, reinterpret_cast<const void*>(param), 0, isProcessRequired, nullptr, nullptr);
}
inline void RequestEvent(int id, bool isProcessRequired = false)
{
RequestEvent(id, nullptr, 0, isProcessRequired, nullptr, nullptr);
}
void EventToQueue(const AZ::HSM::Event& event, unsigned int userDataSize, bool isProcessRequired, EventCommand::DataCloner cloner, EventCommand::DataDeleter deleter);
void ProcessEventOnQueue();
queue<EventCommand> m_eventQueue; ///< Queue with events for the state machine to process.
AZ::HSM m_sm; ///< Hierarchical state machine for the session management;
//////////////////////////////////////////////////////////////////////////
AZStd::chrono::milliseconds m_disconnectKickedPlayersDelay; ///< number of milliseconds before forcing kicked player to disconnect
GridMate::vector<AZStd::pair<TimeStamp, MemberIDCompact>> m_futureKickedPlayers;
};
/**
* Session service interface class.
*/
class SessionService
: public GridMateService
{
friend class GridSession;
friend class Debug::SessionDriller;
friend class GridSearch;
public:
typedef vector<GridSession*> SessionArrayType;
typedef vector<GridSearch*> SearchArrayType;
virtual ~SessionService();
virtual void Update();
virtual bool IsReady() const = 0;
const SessionArrayType& GetSessions() const { return m_sessions; }
IGridMate* GetGridMate() { return m_gridMate; }
protected:
SessionService(const SessionServiceDesc& desc);
// GridMate service
void OnServiceRegistered(IGridMate* gridMate) override;
void OnServiceUnregistered(IGridMate* gridMate) override;
void OnGridMateUpdate(IGridMate* gridMate) override;
/// Called when we create a session (from Session constructor)
void AddSession(GridSession* session);
/// Called once we destroy a session
void RemoveSession(GridSession* session);
/// Called when we start a grid search (from Session constructor)
void AddGridSeach(GridSearch* search);
/// Called when we release a grid search
void ReleaseGridSearch(GridSearch* search);
SessionArrayType m_sessions;
SearchArrayType m_activeSearches;
SearchArrayType m_completedSearches;
IGridMate* m_gridMate;
};
/**
* Interface for a session search.
*
* \note you need to make sure you release the search after you are done using it.
*/
class GridSearch
{
friend class SessionService;
public:
GridSearch(SessionService* service)
: m_sessionService(service)
, m_isDone(false)
{
AZ_Assert(m_sessionService, "Invalid session service");
m_sessionService->AddGridSeach(this);
}
virtual ~GridSearch() {}
/// Return true if the search has finished, otherwise false.
bool IsDone() const { return m_isDone; }
virtual unsigned int GetNumResults() const = 0;
virtual const SearchInfo* GetResult(unsigned int index) const = 0;
virtual void AbortSearch() = 0;
void Release() { m_sessionService->ReleaseGridSearch(this); }
IGridMate* GetGridMate() const { return m_sessionService->GetGridMate(); }
protected:
virtual void Update() {}
SessionService* m_sessionService;
bool m_isDone;
};
namespace Internal
{
class GridSessionReplica
: public ReplicaChunk
{
typedef DataSet<AZ::u8> BasicUChar;
typedef DataSet<AZ::u32> BasicUInt;
friend class GridMate::GridSession;
public:
GM_CLASS_ALLOCATOR(GridSessionReplica);
GridSessionReplica(GridSession* session = nullptr)
: m_numUsedPrivateSlots("NumUsedPrivateSlots")
, m_numUsedPublicSlots("NumUsedPublicSlots")
, m_numFreePrivateSlots("NumFreePrivateSlots")
, m_numFreePublicSlots("NumFreePublicSlots")
, m_peerToPeerTimeout("PeerToPeerTimeout", 10000)
, m_hostMigrationTimeout("HostMigrationTimeout", 10000)
, m_hostMigrationVotingTime("HostMigrationVotingTime")
, m_flags("Flags")
, m_topology("Topology", ST_INVALID)
, m_params("Params")
, m_isDisconnectDetection("DisconnectDetection", true)
, m_session(session)
{
if (m_session)
{
m_isDisconnectDetection.Set(m_session->GetCarrierDesc().m_enableDisconnectDetection);
}
SetPriority(k_replicaPriorityRealTime);
}
bool IsReplicaMigratable() override
{
return true;
}
bool IsBroadcast() override { return true; }
typedef AZStd::fixed_vector<GridSessionParam, SessionParams::k_maxNumParams> ParamContainer;
BasicUChar m_numUsedPrivateSlots;
BasicUChar m_numUsedPublicSlots;
BasicUChar m_numFreePrivateSlots;
BasicUChar m_numFreePublicSlots;
BasicUInt m_peerToPeerTimeout;
BasicUInt m_hostMigrationTimeout;
BasicUInt m_hostMigrationVotingTime;
BasicUChar m_flags;
DataSet<SessionTopology> m_topology;
class ParamMarshaler
{
public:
AZ_FORCE_INLINE void Marshal(WriteBuffer& wb, const GridSessionParam& param) const
{
wb.Write(param.m_id);
wb.Write(param.m_value);
wb.Write(param.m_type);
}
AZ_FORCE_INLINE void Unmarshal(GridSessionParam& param, ReadBuffer& rb) const
{
rb.Read(param.m_id);
rb.Read(param.m_value);
rb.Read(param.m_type);
}
};
DataSet<ParamContainer, ContainerMarshaler<ParamContainer, ParamMarshaler> > m_params; ///< Session params.
DataSet<bool> m_isDisconnectDetection; ///< Allows to control disconnect detection states in the entire session.
protected:
GridSession* m_session;
};
/**
*
*/
class GridMemberStateReplica
: public ReplicaChunk
{
friend class GridMate::GridMember;
friend class GridMate::GridSession;
public:
GM_CLASS_ALLOCATOR(GridMemberStateReplica);
GridMemberStateReplica(GridMember* member = nullptr);
void OnReplicaActivate(const ReplicaContext& rc) override;
void OnReplicaDeactivate(const ReplicaContext& rc) override;
/// Called during host migration from the new Host. It will indicate which session you need to join.
bool OnNewHost(const SessionID& sessionId, const RpcContext&);
bool IsReplicaMigratable() override { return false; }
bool IsBroadcast() override { return true; }
GridMember* m_member;
typedef vector<MemberIDCompact> MuteListType;
typedef DataSet<MuteListType, ContainerMarshaler<MuteListType> > MuteDataSetType;
Rpc<RpcArg<const SessionID&> >::BindInterface<GridMemberStateReplica, &GridMemberStateReplica::OnNewHost, RpcAuthoritativeTraits> OnNewHostRpc;
DataSet<AZ::u8> m_numConnections;
DataSet<NatType> m_natType;
DataSet<string> m_name;
DataSet<MemberIDCompact> m_memberId;
DataSet<MemberIDCompact> m_newHostVote; ///< Used when in host migration, to cast machine's vote.
MuteDataSetType m_muteList; ///< List of all players we have muted.
// Platform and application informational data
DataSet<AZ::PlatformID, ConversionMarshaler<AZ::u8, AZ::PlatformID> > m_platformId;
DataSet<string> m_machineName;
DataSet<AZ::u32> m_processId;
DataSet<bool> m_isInvited;
};
}
namespace Debug
{
/**
* Session driller events,
* this events are in addition to the session event bus
*/
class SessionDrillerEvents
: public AZ::Debug::DrillerEBusTraits
{
public:
virtual ~SessionDrillerEvents() {}
/// Callback that is called when the Session service is ready to process sessions.
virtual void OnSessionServiceReady() {}
//virtual OnCommucationChanged() = 0 Callback that notifies the title when a member's communication settings change.
/// Callback when we start a grid search.
virtual void OnGridSearchStart(GridSearch* gridSearch) { (void)gridSearch; }
/// Callback that notifies the title when a game search query have completed.
virtual void OnGridSearchComplete(GridSearch* gridSearch) { (void)gridSearch; }
/// Callback when we release (delete) a grid search. It's not safe to hold the grid pointer after this.
virtual void OnGridSearchRelease(GridSearch* gridSearch) { (void)gridSearch; }
/// Callback that notifies the title when a new member joins the game session.
virtual void OnMemberJoined(GridSession* session, GridMember* member) { (void)session; (void)member; }
/// Callback that notifies the title that a member is leaving the game session. member pointer is NOT valid after the callback returns.
virtual void OnMemberLeaving(GridSession* session, GridMember* member) { (void)session; (void)member; }
// \todo a better way will be (after we solve migration) is to supply a reason to OnMemberLeaving... like the member was kicked.
// this will require that we actually remove the replica at the same moment.
/// Callback that host decided to kick a member. You will receive a OnMemberLeaving when the actual member leaves the session.
virtual void OnMemberKicked(GridSession* session, GridMember* member) { (void)session; (void)member; }
/// After this callback it is safe to access session features. If host session is fully operational if client wait for OnSessionJoined.
virtual void OnSessionCreated(GridSession* session) { (void)session; }
/// Called on client machines to indicate that we join successfully.
virtual void OnSessionJoined(GridSession* session) { (void)session; }
/// Callback that notifies the title when a session will be left. session pointer is NOT valid after the callback returns.
virtual void OnSessionDelete(GridSession* session) { (void)session; }
/// Called when a session error occurs.
virtual void OnSessionError(GridSession* session, const string& errorMsg) { (void)session; (void)errorMsg; }
/// Called when the actual game(match) starts
virtual void OnSessionStart(GridSession* session) { (void)session; }
/// Called when the actual game(match) ends
virtual void OnSessionEnd(GridSession* session) { (void)session; }
/// Called when we start a host migration.
virtual void OnMigrationStart(GridSession* session) { (void)session; }
/// Called so the user can select a member that should be the new Host. Value will be ignored if NULL, current host or the member has invalid connection id.
virtual void OnMigrationElectHost(GridSession* session, GridMember*& newHost) { (void)session; (void)newHost; }
/// Called when the host migration has completed.
virtual void OnMigrationEnd(GridSession* session, GridMember* newHost) { (void)session; (void)newHost; }
/// Called when we have our last chance to write statistics data for member in the session.
virtual void OnWriteStatistics(GridSession* session, GridMember* member, StatisticsData& data) { (void)session; (void)member; (void)data; }
};
typedef AZ::EBus<SessionDrillerEvents> SessionDrillerBus;
}
} // namespace GridMate
#endif // GM_SESSION_H