38261d0800
* Updated all copyright headers to split the longer original copyright line into 2 shorter lines Signed-off-by: Steve Pham <spham@amazon.com>
1202 lines
41 KiB
C++
1202 lines
41 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <AzCore/std/smart_ptr/intrusive_ptr.h>
|
|
#include <AzCore/std/string/conversions.h>
|
|
|
|
#include <AzCore/Math/Crc.h>
|
|
#include <AzCore/Math/Uuid.h>
|
|
|
|
#include <GridMate/Carrier/SocketDriver.h>
|
|
#include <GridMate/Carrier/Utils.h>
|
|
|
|
#include <GridMate/Replica/Replica.h>
|
|
#include <GridMate/Replica/ReplicaChunkDescriptor.h>
|
|
#include <GridMate/Replica/ReplicaFunctions.h>
|
|
|
|
#include <GridMate/Serialize/DataMarshal.h>
|
|
|
|
#include <GridMate/Session/LANSession.h>
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Implementations
|
|
namespace GridMate
|
|
{
|
|
class LANMember;
|
|
/**
|
|
* Member ID is unique identifier for a member in the session.
|
|
* \note we can typedef MemberID to a string, this will be as generic as it gets!
|
|
*/
|
|
struct LANMemberID
|
|
: public MemberID
|
|
{
|
|
friend class LANMemberIDMarshaler;
|
|
|
|
static LANMemberID Create(MemberIDCompact id, const string& address)
|
|
{
|
|
LANMemberID mid;
|
|
mid.m_id = id;
|
|
mid.m_address = address;
|
|
return mid;
|
|
}
|
|
|
|
void SetAddress(const string& address)
|
|
{
|
|
m_address = address;
|
|
}
|
|
|
|
MemberIDCompact GetID() const { return m_id; }
|
|
|
|
virtual string ToString() const
|
|
{
|
|
return string::format("%x", m_id);
|
|
}
|
|
virtual string ToAddress() const { return m_address; }
|
|
virtual MemberIDCompact Compact() const { return m_id; }
|
|
virtual bool IsValid() const { return m_id != 0; }
|
|
|
|
private:
|
|
MemberIDCompact m_id;
|
|
string m_address;
|
|
};
|
|
|
|
class LANMemberIDMarshaler
|
|
{
|
|
public:
|
|
void Marshal(WriteBuffer& wb, const LANMemberID& id) const
|
|
{
|
|
wb.Write(id.m_id);
|
|
}
|
|
void Unmarshal(LANMemberID& id, ReadBuffer& rb) const
|
|
{
|
|
rb.Read(id.m_id);
|
|
}
|
|
};
|
|
|
|
class LANSessionReplica;
|
|
|
|
struct LANSessionMsg
|
|
{
|
|
LANSessionMsg(const VersionType& version)
|
|
: m_version(version)
|
|
, m_message(0)
|
|
{}
|
|
|
|
bool ValidateVersion(const VersionType& version) { return m_version == version; }
|
|
|
|
enum MessageType
|
|
{
|
|
MT_SEARCH = 0, ///< Called from a LAN client searches for LAN sessions.
|
|
MT_SEARCH_RESULT,
|
|
// Other messages are communicated trough replicas.
|
|
};
|
|
|
|
VersionType m_version; ///< Session crc
|
|
AZ::u8 m_message; ///< messsage \ref MessageType
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class LANSession
|
|
: public GridSession
|
|
{
|
|
friend class LANSessionService;
|
|
friend class LANSessionReplica;
|
|
friend class LANMember;
|
|
public:
|
|
GM_CLASS_ALLOCATOR(LANSession);
|
|
|
|
void Update() override;
|
|
|
|
// TEMP so the factory can have access to the function.
|
|
bool AddMember(GridMember* member) override { return GridSession::AddMember(member); }
|
|
|
|
/// Creates the local player.
|
|
GridMember* CreateLocalMember(bool isHost, bool isInvited, RemotePeerMode peerMode);
|
|
/// Creates remote player, when he wants to join.
|
|
GridMember* CreateRemoteMember(const string& address, ReadBuffer& data, RemotePeerMode peerMode, ConnectionID connId = InvalidConnectionID) override;
|
|
|
|
/// Called when we receive the session replica. We create one and return the pointer.
|
|
LANSessionReplica* OnSessionReplicaArrived();
|
|
|
|
/// Called when session parameters have changed.
|
|
void OnSessionParamChanged(const GridSessionParam& param) override { (void)param; }
|
|
void OnSessionParamRemoved(const string& paramId) override { (void)paramId; }
|
|
|
|
private:
|
|
explicit LANSession(LANSessionService* service);
|
|
~LANSession() override;
|
|
|
|
/// Hosting a session.
|
|
bool Initialize(const LANSessionParams& params, const CarrierDesc& carrierDesc);
|
|
|
|
/// Joining a session.
|
|
bool Initialize(const LANSearchInfo& info, const JoinParams& params, const CarrierDesc& carrierDesc);
|
|
|
|
bool MatchMake(const LANSearchParams& sp);
|
|
|
|
string MakeSessionId();
|
|
|
|
Driver* m_driver; ///< Driver for network searches
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// State machine
|
|
// just override a few state handlers, the right way would be to sub state the
|
|
// current ones. But this is less work.
|
|
bool OnStateCreate(AZ::HSM& sm, const AZ::HSM::Event& e) override;
|
|
bool OnStateDelete(AZ::HSM& sm, const AZ::HSM::Event& e) override;
|
|
bool OnStateHostMigrateSession(AZ::HSM& sm, const AZ::HSM::Event& e) override;
|
|
//////////////////////////////////////////////////////////////////////////
|
|
};
|
|
|
|
/**
|
|
* \note many features will be common for most platforms, make sure they are moved to
|
|
* a base class, where users can reuse and enhance them.
|
|
*/
|
|
class LANSessionReplica
|
|
: public Internal::GridSessionReplica
|
|
{
|
|
public:
|
|
class LANSessionReplicaDesc
|
|
: public ReplicaChunkDescriptor
|
|
{
|
|
public:
|
|
LANSessionReplicaDesc()
|
|
: ReplicaChunkDescriptor(LANSessionReplica::GetChunkName(), sizeof(LANSessionReplica))
|
|
{
|
|
}
|
|
|
|
ReplicaChunkBase* CreateFromStream(UnmarshalContext& mc) override
|
|
{
|
|
LANSession* session = static_cast<LANSession*>(static_cast<GridSession*>(mc.m_rm->GetUserContext(AZ_CRC("GridSession", 0x099df4e6))));
|
|
AZ_Assert(session, "We need to have a valid session!");
|
|
return session->OnSessionReplicaArrived();
|
|
}
|
|
|
|
void DiscardCtorStream(UnmarshalContext&) override {}
|
|
void DeleteReplicaChunk(ReplicaChunkBase* chunkInstance) override { delete chunkInstance; }
|
|
void MarshalCtorData(ReplicaChunkBase*, WriteBuffer&) override {}
|
|
};
|
|
|
|
GM_CLASS_ALLOCATOR(LANSessionReplica);
|
|
static const char* GetChunkName() { return "GridMateLANSessionReplica"; }
|
|
|
|
LANSessionReplica(LANSession* session)
|
|
: GridSessionReplica(session)
|
|
, m_hostPort("HostPort")
|
|
{}
|
|
|
|
DataSet<AZ::u32> m_hostPort; ///< Port on which the host should provide matchmaking services.
|
|
};
|
|
|
|
struct SessionMemberInfoCtorContext
|
|
: public CtorContextBase
|
|
{
|
|
CtorDataSet<MemberIDCompact> m_memberId;
|
|
CtorDataSet<string> m_memberAddress; ///< As the server/host sees it!
|
|
CtorDataSet<RemotePeerMode> m_peerMode;
|
|
CtorDataSet<bool> m_isHost;
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class LANMember
|
|
: public GridMember
|
|
{
|
|
friend class LANSession;
|
|
friend class LANMemberState;
|
|
|
|
public:
|
|
class LANMemberDesc
|
|
: public ReplicaChunkDescriptor
|
|
{
|
|
public:
|
|
LANMemberDesc()
|
|
: ReplicaChunkDescriptor(LANMember::GetChunkName(), sizeof(LANMember))
|
|
{
|
|
}
|
|
|
|
ReplicaChunkBase* CreateFromStream(UnmarshalContext& mc) override
|
|
{
|
|
SessionMemberInfoCtorContext ctorContext;
|
|
ctorContext.Unmarshal(*mc.m_iBuf);
|
|
|
|
LANSession* session = static_cast<LANSession*>(static_cast<GridSession*>(mc.m_rm->GetUserContext(AZ_CRC("GridSession", 0x099df4e6))));
|
|
AZ_Assert(session, "We need to have a valid session!");
|
|
LANMember* member;
|
|
MemberIDCompact memberId = ctorContext.m_memberId.Get();
|
|
string memberAddress = ctorContext.m_memberAddress.Get();
|
|
RemotePeerMode remotePeerMode = ctorContext.m_peerMode.Get();
|
|
bool isMemberHost = ctorContext.m_isHost.Get();
|
|
|
|
if (memberId != static_cast<const LANMemberID&>(session->GetMyMember()->GetId()).GetID())
|
|
{
|
|
ReadBuffer rb(EndianType::IgnoreEndian, reinterpret_cast<const char*>(&memberId), sizeof(memberId));
|
|
member = static_cast<LANMember*>(session->CreateRemoteMember(memberAddress, rb, remotePeerMode, isMemberHost ? mc.m_peer->GetConnectionId() : InvalidConnectionID));
|
|
}
|
|
else
|
|
{
|
|
// just bind our local member
|
|
member = static_cast<LANMember*>(session->GetMyMember());
|
|
}
|
|
bool isAdded = session->AddMember(member);
|
|
AZ_Assert(isAdded, "Failed to add a member, there is something wrong with the member replicas!");
|
|
if (!isAdded)
|
|
{
|
|
member = nullptr;
|
|
}
|
|
return member;
|
|
}
|
|
|
|
void DiscardCtorStream(UnmarshalContext& mc) override
|
|
{
|
|
SessionMemberInfoCtorContext ctorContext;
|
|
ctorContext.Unmarshal(*mc.m_iBuf);
|
|
}
|
|
|
|
void DeleteReplicaChunk(ReplicaChunkBase* chunkInstance) override
|
|
{
|
|
if (!static_cast<LANMember*>(chunkInstance)->IsLocal())
|
|
{
|
|
delete chunkInstance;
|
|
}
|
|
}
|
|
|
|
void MarshalCtorData(ReplicaChunkBase* chunkInstance, WriteBuffer& wb) override
|
|
{
|
|
LANMember* member = static_cast<LANMember*>(chunkInstance);
|
|
SessionMemberInfoCtorContext ctorContext;
|
|
ctorContext.m_memberId.Set(member->m_memberId.GetID());
|
|
ctorContext.m_memberAddress.Set(member->m_memberId.ToAddress());
|
|
ctorContext.m_peerMode.Set(member->m_peerMode.Get());
|
|
ctorContext.m_isHost.Set(member->IsHost());
|
|
ctorContext.Marshal(wb);
|
|
}
|
|
};
|
|
|
|
GM_CLASS_ALLOCATOR(LANMember);
|
|
static const char* GetChunkName() { return "GridMateLANMember"; }
|
|
|
|
/// 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 { return m_memberId; }
|
|
/// returns a base player id, it's implementation is platform dependent. (NOT supported)
|
|
virtual const PlayerId* GetPlayerId() const { return nullptr; }
|
|
|
|
/// Remote member ctor.
|
|
LANMember(ConnectionID connId, const LANMemberID& id, LANSession* session);
|
|
/// Local member ctor.
|
|
LANMember(const LANMemberID& id, LANSession* session);
|
|
|
|
LANMemberID m_memberId;
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class LANMemberState
|
|
: public Internal::GridMemberStateReplica
|
|
{
|
|
public:
|
|
GM_CLASS_ALLOCATOR(LANMemberState);
|
|
static const char* GetChunkName() { return "GridMateLANMemberState"; }
|
|
|
|
LANMemberState(GridMember* member = nullptr)
|
|
: GridMemberStateReplica(member)
|
|
{}
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class LANSearch
|
|
: public GridSearch
|
|
{
|
|
friend class LANSessionService;
|
|
public:
|
|
GM_CLASS_ALLOCATOR(LANSearch);
|
|
virtual ~LANSearch();
|
|
|
|
/// Return true if the search has finished, otherwise false.
|
|
virtual unsigned int GetNumResults() const { return static_cast<unsigned int>(m_results.size()); }
|
|
virtual const SearchInfo* GetResult(unsigned int index) const { return &m_results[index]; }
|
|
virtual void AbortSearch();
|
|
|
|
private:
|
|
LANSearch(const LANSearchParams& searchParams, SessionService* service);
|
|
virtual void Update();
|
|
void SearchDone();
|
|
|
|
Driver* m_driver;
|
|
TimeStamp m_timeStart;
|
|
TimeStamp m_lastSearchSend; ///< Time when last search was broadcasted.
|
|
unsigned int m_broadcastInterval; ///< How ofter to send broadcast to find sessions in milliseconds.
|
|
unsigned int m_timeOutMS; ///< Timeout in milliseconds.
|
|
vector<LANSearchInfo> m_results; ///< Search results
|
|
LANSearchParams m_searchParams;
|
|
IGridMate* m_gridMate;
|
|
};
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace GridMate
|
|
{
|
|
namespace Platform
|
|
{
|
|
void AssignExtendedName(GridMate::string& extendedName);
|
|
}
|
|
}
|
|
|
|
using namespace GridMate;
|
|
|
|
//=========================================================================
|
|
// LANMember
|
|
// Local member ctor.
|
|
// [2/1/2011]
|
|
//=========================================================================
|
|
LANMember::LANMember(const LANMemberID& id, LANSession* session)
|
|
: GridMember(id.Compact())
|
|
, m_memberId(id)
|
|
{
|
|
string extendedName;
|
|
Platform::AssignExtendedName(extendedName);
|
|
|
|
m_session = session;
|
|
m_clientState = CreateReplicaChunk<LANMemberState>(this);
|
|
m_clientState->m_name.Set(extendedName);
|
|
NatType natType = NAT_OPEN; // for LAN this is the case
|
|
m_clientState->m_natType.Set(natType);
|
|
|
|
m_clientStateReplica = Replica::CreateReplica(extendedName.c_str());
|
|
m_clientStateReplica->AttachReplicaChunk(m_clientState);
|
|
}
|
|
|
|
//=========================================================================
|
|
// LANMember
|
|
// Remote member ctor.
|
|
// [12/13/2010]
|
|
//=========================================================================
|
|
LANMember::LANMember(ConnectionID connId, const LANMemberID& id, LANSession* session)
|
|
: GridMember(id.Compact())
|
|
, m_memberId(id)
|
|
{
|
|
m_session = session;
|
|
m_connectionId = connId;
|
|
}
|
|
|
|
//=========================================================================
|
|
// LANSession
|
|
// [12/13/2010]
|
|
//=========================================================================
|
|
LANSession::LANSession(LANSessionService* service)
|
|
: GridSession(service)
|
|
, m_driver(nullptr)
|
|
{
|
|
}
|
|
|
|
bool LANSession::Initialize(const LANSessionParams& params, const CarrierDesc& carrierDesc)
|
|
{
|
|
if (!GridSession::Initialize(carrierDesc))
|
|
{
|
|
Shutdown();
|
|
return false;
|
|
}
|
|
|
|
// socket driver to handle session search and matchmake
|
|
LANSessionReplica::ParamContainer sessionParams;
|
|
Driver::ResultCode initResult = Driver::EC_OK;
|
|
if (params.m_port != 0)
|
|
{
|
|
m_driver = aznew SocketDriver(false, true);
|
|
initResult = m_driver->Initialize(m_carrierDesc.m_familyType, params.m_address.c_str(), params.m_port, true, 2048, 2048);
|
|
|
|
if (initResult != Driver::EC_OK)
|
|
{
|
|
Shutdown();
|
|
return false;
|
|
}
|
|
|
|
for (AZStd::size_t i = 0; i < params.m_numParams; ++i)
|
|
{
|
|
sessionParams.push_back(params.m_params[i]);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// start up the session state we will bind it later
|
|
AZ_Assert(params.m_numPublicSlots < 0xff && params.m_numPrivateSlots < 0xff, "Can't have more than 255 slots!");
|
|
AZ_Assert(params.m_numPublicSlots > 0 || params.m_numPrivateSlots > 0, "You don't have any slots open!");
|
|
|
|
LANSessionReplica* state = CreateReplicaChunk<LANSessionReplica>(this);
|
|
state->m_numFreePrivateSlots.Set(static_cast<unsigned char>(params.m_numPrivateSlots));
|
|
state->m_numFreePublicSlots.Set(static_cast<unsigned char>(params.m_numPublicSlots));
|
|
state->m_peerToPeerTimeout.Set(params.m_peerToPeerTimeout);
|
|
state->m_hostMigrationTimeout.Set(params.m_hostMigrationTimeout);
|
|
state->m_hostMigrationVotingTime.Set(AZStd::GetMin(params.m_hostMigrationVotingTime, params.m_hostMigrationTimeout / 2));
|
|
state->m_flags.Set(params.m_flags);
|
|
state->m_topology.Set(params.m_topology);
|
|
state->m_params.Set(sessionParams);
|
|
state->m_hostPort.Set(params.m_port);
|
|
m_state = state;
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
m_myMember = CreateLocalMember(true, true, Mode_Peer);
|
|
|
|
SetUpStateMachine();
|
|
|
|
RequestEventData(SE_HOST, params);
|
|
return true;
|
|
}
|
|
|
|
bool LANSession::Initialize(const LANSearchInfo& info, const JoinParams& params, const CarrierDesc& carrierDesc)
|
|
{
|
|
if (!GridSession::Initialize(carrierDesc))
|
|
{
|
|
Shutdown();
|
|
return false;
|
|
}
|
|
|
|
m_hostAddress = SocketDriverCommon::IPPortToAddressString(info.m_serverIP.c_str(), info.m_serverPort);
|
|
m_sessionId = info.m_sessionId;
|
|
|
|
// start up the session state we will bind it later
|
|
m_state = CreateReplicaChunk<LANSessionReplica>(this);
|
|
|
|
m_myMember = CreateLocalMember(false, false, params.m_desiredPeerMode);
|
|
|
|
SetUpStateMachine();
|
|
|
|
RequestEventData(SE_JOIN, info); // trigger the join event
|
|
return true;
|
|
}
|
|
|
|
//=========================================================================
|
|
// ~LANSession
|
|
// [12/13/2010]
|
|
//=========================================================================
|
|
LANSession::~LANSession()
|
|
{
|
|
delete m_driver;
|
|
m_driver = nullptr;
|
|
}
|
|
|
|
//=========================================================================
|
|
// Update
|
|
// [12/13/2010]
|
|
//=========================================================================
|
|
void
|
|
LANSession::Update()
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Hosting session
|
|
if (m_driver)
|
|
{
|
|
// receive connection requests
|
|
static const int maxDataSize = 2048;
|
|
char data[maxDataSize];
|
|
while (true)
|
|
{
|
|
AZStd::intrusive_ptr<DriverAddress> from;
|
|
Driver::ResultCode result;
|
|
unsigned int recvd = m_driver->Receive(data, maxDataSize, from, &result);
|
|
if (result != Driver::EC_OK)
|
|
{
|
|
AZ_TracePrintf("GridMate", "LANSession::Receive - recvfrom failed with code 0x%08x\n", result);
|
|
break;
|
|
}
|
|
if (recvd == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ReadBuffer rb(kSessionEndian, data, recvd);
|
|
LANSessionMsg msg(m_carrierDesc.m_version);
|
|
rb.Read(msg.m_version);
|
|
if (!msg.ValidateVersion(m_carrierDesc.m_version))
|
|
{
|
|
continue; // wrong message, ignore
|
|
}
|
|
rb.Read(msg.m_message);
|
|
if (msg.m_message == LANSessionMsg::MT_SEARCH)
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Matchmake
|
|
// read the user attributes
|
|
LANSearchParams ssp;
|
|
rb.Read(ssp.m_numParams);
|
|
for (unsigned int i = 0; i < ssp.m_numParams; ++i)
|
|
{
|
|
rb.Read(ssp.m_params[i].m_op);
|
|
rb.Read(ssp.m_params[i].m_id);
|
|
rb.Read(ssp.m_params[i].m_value);
|
|
rb.Read(ssp.m_params[i].m_type);
|
|
}
|
|
|
|
// compare params and filter results.
|
|
if (!MatchMake(ssp))
|
|
{
|
|
continue;
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// send back the session info structure
|
|
LANSearchInfo si;
|
|
si.m_serverPort = static_cast<AZ::u16>(m_carrier ? m_carrier->GetPort() : 0);// send the port for the session
|
|
si.m_numFreePrivateSlots = m_state->m_numFreePrivateSlots.Get();
|
|
si.m_numFreePublicSlots = m_state->m_numFreePublicSlots.Get();
|
|
si.m_numUsedPrivateSlots = m_state->m_numUsedPrivateSlots.Get();
|
|
si.m_numUsedPublicSlots = m_state->m_numUsedPublicSlots.Get();
|
|
|
|
const LANSessionReplica::ParamContainer& params = AZStd::static_pointer_cast<LANSessionReplica>(m_state)->m_params.Get();
|
|
si.m_numParams = static_cast<AZ::u32>(params.size());
|
|
for (AZ::u32 i = 0; i < si.m_numParams; ++i)
|
|
{
|
|
si.m_params[i].m_id = params[i].m_id;
|
|
si.m_params[i].m_value = params[i].m_value;
|
|
si.m_params[i].m_type = params[i].m_type;
|
|
}
|
|
|
|
msg.m_message = LANSessionMsg::MT_SEARCH_RESULT;
|
|
|
|
// write and send session information
|
|
WriteBufferStatic<> wb(kSessionEndian);
|
|
|
|
wb.Write(msg.m_version);
|
|
wb.Write(msg.m_message);
|
|
|
|
wb.Write(si.m_serverPort);
|
|
wb.Write(m_sessionId);
|
|
wb.Write(si.m_numFreePrivateSlots);
|
|
wb.Write(si.m_numFreePublicSlots);
|
|
wb.Write(si.m_numUsedPrivateSlots);
|
|
wb.Write(si.m_numUsedPublicSlots);
|
|
wb.Write(si.m_numParams);
|
|
for (unsigned int i = 0; i < si.m_numParams; ++i)
|
|
{
|
|
wb.Write(si.m_params[i].m_id);
|
|
wb.Write(si.m_params[i].m_value);
|
|
wb.Write(si.m_params[i].m_type);
|
|
}
|
|
|
|
// send back the information.
|
|
result = m_driver->Send(from, wb.Get(), static_cast<unsigned int>(wb.Size()));
|
|
if (result != Driver::EC_OK)
|
|
{
|
|
AZ_TracePrintf("GridMate", "LANSession::Send - sendto failed with code 0x%08x at %s", result, from->ToString().c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "We don't handle %d message!", msg.m_message);
|
|
}
|
|
}
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// THIS should be last in the update, it might delete the session internally (this pointer will become invalid!
|
|
GridSession::Update();
|
|
}
|
|
|
|
//=========================================================================
|
|
// CompareParams
|
|
// [7/15/2013]
|
|
//=========================================================================
|
|
template<class Param>
|
|
inline bool CompareParams(const Param& left, const Param& right, GridSessionSearchOperators op)
|
|
{
|
|
switch (op)
|
|
{
|
|
case GridSessionSearchOperators::SSO_OPERATOR_EQUAL:
|
|
if (left != right)
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
case GridSessionSearchOperators::SSO_OPERATOR_NOT_EQUAL:
|
|
if (left == right)
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
case GridSessionSearchOperators::SSO_OPERATOR_GREATER_EQUAL_THAN:
|
|
if (left < right)
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
case GridSessionSearchOperators::SSO_OPERATOR_GREATER_THAN:
|
|
if (left <= right)
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
case GridSessionSearchOperators::SSO_OPERATOR_LESS_EQUAL_THAN:
|
|
if (left > right)
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
case GridSessionSearchOperators::SSO_OPERATOR_LESS_THAN:
|
|
if (left >= right)
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
AZ_Warning("GridMate", false, "Invalid operator type %d", op);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//=========================================================================
|
|
// MatchMake
|
|
// [12/13/2010]
|
|
//=========================================================================
|
|
bool
|
|
LANSession::MatchMake(const LANSearchParams& sp)
|
|
{
|
|
bool isGoodMatch = true;
|
|
for (unsigned int i = 0; i < sp.m_numParams; ++i)
|
|
{
|
|
// find parameter
|
|
const LANSessionReplica::ParamContainer& params = AZStd::static_pointer_cast<LANSessionReplica>(m_state)->m_params.Get();
|
|
AZStd::size_t j = 0;
|
|
for (; j < params.size(); ++j)
|
|
{
|
|
if (sp.m_params[i].m_id == params[j].m_id && sp.m_params[i].m_type == params[j].m_type)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (j == params.size())
|
|
{
|
|
isGoodMatch = false; // parameter was NOT found
|
|
break;
|
|
}
|
|
|
|
switch (sp.m_params[i].m_type)
|
|
{
|
|
case GridSessionParam::VT_INT32:
|
|
{
|
|
AZ::s32 left = AZStd::stoi(params[j].m_value);
|
|
AZ::s32 right = AZStd::stoi(sp.m_params[i].m_value);
|
|
isGoodMatch = CompareParams(left, right, sp.m_params[i].m_op);
|
|
break;
|
|
};
|
|
case GridSessionParam::VT_INT64:
|
|
{
|
|
AZ::s64 left = AZStd::stoll(params[j].m_value);
|
|
AZ::s64 right = AZStd::stoll(sp.m_params[i].m_value);
|
|
isGoodMatch = CompareParams(left, right, sp.m_params[i].m_op);
|
|
break;
|
|
};
|
|
case GridSessionParam::VT_FLOAT:
|
|
{
|
|
float left = AZStd::stof(params[j].m_value);
|
|
float right = AZStd::stof(sp.m_params[i].m_value);
|
|
isGoodMatch = CompareParams(left, right, sp.m_params[i].m_op);
|
|
break;
|
|
};
|
|
case GridSessionParam::VT_DOUBLE:
|
|
{
|
|
double left = AZStd::stof(params[j].m_value);
|
|
double right = AZStd::stof(sp.m_params[i].m_value);
|
|
isGoodMatch = CompareParams(left, right, sp.m_params[i].m_op);
|
|
break;
|
|
};
|
|
case GridSessionParam::VT_STRING:
|
|
{
|
|
isGoodMatch = CompareParams(params[j].m_value, sp.m_params[i].m_value, sp.m_params[i].m_op);
|
|
break;
|
|
};
|
|
default:
|
|
AZ_Assert(false, "Unsupported parameter type %d", params[j].m_type);
|
|
isGoodMatch = false;
|
|
}
|
|
|
|
if (!isGoodMatch)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return isGoodMatch;
|
|
}
|
|
|
|
//=========================================================================
|
|
// CreateLocalMember
|
|
// [2/1/2011]
|
|
//=========================================================================
|
|
GridMember*
|
|
LANSession::CreateLocalMember(bool isHost, bool isInvited, RemotePeerMode peerMode)
|
|
{
|
|
AZ_Assert(m_myMember == nullptr, "We already have added a local member!");
|
|
|
|
string ip = Utils::GetMachineAddress(m_carrierDesc.m_familyType);
|
|
string address = SocketDriverCommon::IPPortToAddressString(ip.c_str(), m_carrierDesc.m_port);
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// TODO: Use the UUID as an ID, we will need to add marshalers and so on
|
|
AZ::Uuid uuid = AZ::Uuid::CreateRandom();
|
|
MemberIDCompact id = AZ::Crc32(&uuid.data, sizeof(uuid.data));
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
LANMemberID myId = LANMemberID::Create(id, address);
|
|
|
|
LANMember* member = CreateReplicaChunk<LANMember>(myId, this);
|
|
member->SetHost(isHost);
|
|
member->SetInvited(isInvited);
|
|
member->m_peerMode.Set(peerMode);
|
|
return member;
|
|
}
|
|
|
|
//=========================================================================
|
|
// CreateRemoteMember
|
|
// [2/2/2011]
|
|
//==========================================================================
|
|
GridMember*
|
|
LANSession::CreateRemoteMember(const string& address, ReadBuffer& data, RemotePeerMode peerMode, ConnectionID connId)
|
|
{
|
|
MemberIDCompact id;
|
|
data.Read(id);
|
|
LANMemberID memberId = LANMemberID::Create(id, address);
|
|
AZ_Warning("GridMate", GetTopology() != ST_INVALID, "Invalid sesson topology! Did session replica arrive yet?");
|
|
if (GetTopology() == ST_PEER_TO_PEER && peerMode == Mode_Peer && static_cast<LANMember*>(m_myMember)->m_peerMode.Get() == Mode_Peer && connId == InvalidConnectionID)
|
|
{
|
|
connId = m_carrier->Connect(memberId.ToAddress());
|
|
}
|
|
|
|
return CreateReplicaChunk<LANMember>(connId, memberId, this);
|
|
}
|
|
|
|
//=========================================================================
|
|
// OnSessionReplicaArrived
|
|
// [2/1/2011]
|
|
//=========================================================================
|
|
LANSessionReplica*
|
|
LANSession::OnSessionReplicaArrived()
|
|
{
|
|
AZ_TracePrintf("GridMate", "(%s - %s) has joined session: %s\n", m_myMember->GetId().ToString().c_str(), m_myMember->GetId().ToAddress().c_str(),
|
|
m_sessionId.c_str());
|
|
// join is complete
|
|
RequestEvent(LANSession::SE_JOINED);
|
|
return static_cast<LANSessionReplica*>(m_state.get());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Session state machine overrides
|
|
bool
|
|
LANSession::OnStateCreate(AZ::HSM& sm, const AZ::HSM::Event& e)
|
|
{
|
|
bool isProcessed = GridSession::OnStateCreate(sm, e);
|
|
|
|
switch (e.id)
|
|
{
|
|
case AZ::HSM::EnterEventId:
|
|
{
|
|
// patch the ID if we use implicit port
|
|
AZ_Assert(m_carrier, "Carrier must be created!");
|
|
string ip;
|
|
unsigned int port;
|
|
SocketDriverCommon::AddressStringToIPPort(m_myMember->GetId().ToAddress(), ip, port);
|
|
AZ_Assert(port == 0 || port == m_carrier->GetPort(), "Carrier port missmatch! It should either be 0 (and patched here) in the implicit bind or the port number for explicit bind!");
|
|
if (port == 0)
|
|
{
|
|
static_cast<LANMember*>(m_myMember)->m_memberId.SetAddress(SocketDriverCommon::IPPortToAddressString(ip.c_str(), m_carrier->GetPort()));
|
|
}
|
|
|
|
// Set my ID as userdata for the handshake (so we can proparage to the host when we connect)
|
|
MemberIDCompact id = static_cast<const LANMemberID&>(m_myMember->GetId()).GetID();
|
|
WriteBufferStatic<> wb(kSessionEndian);
|
|
wb.Write(id);
|
|
SetHandshakeUserData(wb.Get(), wb.Size());
|
|
|
|
if (IsHost())
|
|
{
|
|
m_sessionId = MakeSessionId();
|
|
}
|
|
|
|
RequestEvent(SE_CREATED); // just send create even
|
|
} return true;
|
|
}
|
|
;
|
|
return isProcessed;
|
|
}
|
|
bool
|
|
LANSession::OnStateDelete(AZ::HSM& sm, const AZ::HSM::Event& e)
|
|
{
|
|
bool isProcessed = GridSession::OnStateDelete(sm, e);
|
|
|
|
switch (e.id)
|
|
{
|
|
case AZ::HSM::EnterEventId:
|
|
{
|
|
RequestEvent(SE_DELETED);
|
|
} return true;
|
|
}
|
|
|
|
return isProcessed;
|
|
}
|
|
bool
|
|
LANSession::OnStateHostMigrateSession(AZ::HSM& sm, const AZ::HSM::Event& e)
|
|
{
|
|
bool isProcessed = GridSession::OnStateHostMigrateSession(sm, e);
|
|
|
|
switch (e.id)
|
|
{
|
|
case AZ::HSM::EnterEventId:
|
|
{
|
|
if (m_sessionId.empty())
|
|
{
|
|
// we are the new host create a new session and let the rest of the clients know.
|
|
m_sessionId = MakeSessionId();
|
|
|
|
// possibly reset the room slots and reassign them after host migration is complete.
|
|
RequestEvent(SE_HM_SESSION_MIGRATED);
|
|
}
|
|
else
|
|
{
|
|
// we are a client and we need to migrate the session to the new session ID
|
|
RequestEvent(SE_HM_CLIENT_SESSION_MIGRATED);
|
|
}
|
|
} return true;
|
|
case AZ::HSM::ExitEventId:
|
|
{
|
|
if (IsHost())
|
|
{
|
|
AZ_Assert(m_driver == nullptr, "If we just became the host we should not have a matchmaking services enabled!");
|
|
unsigned int hostPort = AZStd::static_pointer_cast<LANSessionReplica>(m_state)->m_hostPort.Get();
|
|
// socket driver to handle session search and matchmake...
|
|
if (hostPort != 0)
|
|
{
|
|
m_driver = aznew SocketDriver(false, true);
|
|
// TODO: To support Ipv6 properly we need to store the family and bind address in the LANSessionReplica
|
|
if (m_driver->Initialize(Driver::BSD_AF_INET, nullptr, hostPort, true) != Driver::EC_OK)
|
|
{
|
|
// check the output for more info
|
|
string errorMsg = string::format("Failed to initialize socket at port %d!", hostPort);
|
|
EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnSessionError, this, errorMsg);
|
|
EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnSessionError, this, errorMsg);
|
|
// We can't be a real host if we failed to provide matching services.
|
|
Leave(false);
|
|
}
|
|
}
|
|
}
|
|
} return true;
|
|
}
|
|
|
|
return isProcessed;
|
|
}
|
|
|
|
//=========================================================================
|
|
// MakeSessionId
|
|
// [3/7/2013]
|
|
//=========================================================================
|
|
string
|
|
LANSession::MakeSessionId()
|
|
{
|
|
char temp[64];
|
|
AZ::Uuid::CreateRandom().ToString(temp, AZ_ARRAY_SIZE(temp), false, false);
|
|
return temp;
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// LANSearch
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//=========================================================================
|
|
// LANSearch
|
|
// [12/13/2010]
|
|
//=========================================================================
|
|
LANSearch::LANSearch(const LANSearchParams& searchParams, SessionService* service)
|
|
: GridSearch(service)
|
|
, m_timeOutMS(searchParams.m_timeOutMs)
|
|
, m_searchParams(searchParams)
|
|
{
|
|
m_gridMate = service->GetGridMate();
|
|
AZ_Assert(m_gridMate, "SessionService is not bound to GridMate instance");
|
|
m_results.reserve(m_searchParams.m_maxSessions);
|
|
|
|
m_driver = aznew SocketDriver(false, true);
|
|
if (m_driver->Initialize(searchParams.m_familyType, searchParams.m_listenAddress.c_str(), searchParams.m_listenPort, true) != Driver::EC_OK)
|
|
{
|
|
SearchDone();
|
|
}
|
|
|
|
m_timeStart = AZStd::chrono::system_clock::now();
|
|
m_broadcastInterval = searchParams.m_broadcastFrequencyMs;
|
|
|
|
Update(); // Call an instant update to send a broadcast
|
|
}
|
|
|
|
//=========================================================================
|
|
// ~LANSearch
|
|
// [12/13/2010]
|
|
//=========================================================================
|
|
LANSearch::~LANSearch()
|
|
{
|
|
if (!IsDone())
|
|
{
|
|
AbortSearch();
|
|
}
|
|
}
|
|
|
|
|
|
//=========================================================================
|
|
// Update
|
|
// [12/13/2010]
|
|
//=========================================================================
|
|
void
|
|
LANSearch::Update()
|
|
{
|
|
if (m_isDone)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TimeStamp now = AZStd::chrono::system_clock::now();
|
|
|
|
// broadcast the LAN search.
|
|
AZStd::chrono::milliseconds timeElapsed = now - m_lastSearchSend;
|
|
if (timeElapsed.count() >= m_broadcastInterval)
|
|
{
|
|
m_lastSearchSend = now;
|
|
|
|
// write and send session information
|
|
WriteBufferStatic<> wb(kSessionEndian);
|
|
LANSessionMsg msg(m_searchParams.m_version);
|
|
msg.m_message = LANSessionMsg::MT_SEARCH;
|
|
wb.Write(msg.m_version);
|
|
wb.Write(msg.m_message);
|
|
|
|
// Matchmake
|
|
// don't send the broadcast and listen ports
|
|
wb.Write(m_searchParams.m_numParams);
|
|
for (unsigned int i = 0; i < m_searchParams.m_numParams; ++i)
|
|
{
|
|
wb.Write(m_searchParams.m_params[i].m_op);
|
|
wb.Write(m_searchParams.m_params[i].m_id);
|
|
wb.Write(m_searchParams.m_params[i].m_value);
|
|
wb.Write(m_searchParams.m_params[i].m_type);
|
|
}
|
|
|
|
string serverAddress = m_searchParams.m_serverAddress;
|
|
if (serverAddress.empty())
|
|
{
|
|
serverAddress = Utils::GetBroadcastAddress(m_searchParams.m_familyType);
|
|
}
|
|
Driver::ResultCode result = m_driver->Send(m_driver->CreateDriverAddress(SocketDriverCommon::IPPortToAddressString(serverAddress.c_str(), m_searchParams.m_serverPort)), wb.Get(), static_cast<unsigned int>(wb.Size()));
|
|
if (result != Driver::EC_OK)
|
|
{
|
|
AZ_TracePrintf("GridMate", "LANSearch::Send - to %s port %d failed with code 0x%08x\n", serverAddress.c_str(), m_searchParams.m_serverPort, result);
|
|
}
|
|
}
|
|
|
|
// Receive all the
|
|
static const int k_maxDataSize = 2048;
|
|
char data[k_maxDataSize];
|
|
bool haveMaxResults = false;
|
|
while (true)
|
|
{
|
|
AZStd::intrusive_ptr<DriverAddress> from;
|
|
Driver::ResultCode result;
|
|
unsigned int recvd = m_driver->Receive(data, k_maxDataSize, from, &result);
|
|
if (result != Driver::EC_OK)
|
|
{
|
|
AZ_TracePrintf("GridMate", "LANSearch::Receive - recvfrom failed with code 0x%08x\n", result);
|
|
break;
|
|
}
|
|
if (recvd == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ReadBuffer rb(kSessionEndian, data, recvd);
|
|
LANSessionMsg msg(m_searchParams.m_version);
|
|
rb.Read(msg.m_version);
|
|
if (!msg.ValidateVersion(m_searchParams.m_version))
|
|
{
|
|
continue; // wrong message, ignore
|
|
}
|
|
rb.Read(msg.m_message);
|
|
|
|
if (msg.m_message != LANSessionMsg::MT_SEARCH_RESULT)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LANSearchInfo si;
|
|
unsigned int port; // we don't use that port (as this is the search port)
|
|
SocketDriverCommon::AddressStringToIPPort(from->ToAddress(), si.m_serverIP, port);
|
|
rb.Read(si.m_serverPort);
|
|
rb.Read(si.m_sessionId);
|
|
rb.Read(si.m_numFreePrivateSlots);
|
|
rb.Read(si.m_numFreePublicSlots);
|
|
rb.Read(si.m_numUsedPrivateSlots);
|
|
rb.Read(si.m_numUsedPublicSlots);
|
|
rb.Read(si.m_numParams);
|
|
for (unsigned int i = 0; i < si.m_numParams; ++i)
|
|
{
|
|
rb.Read(si.m_params[i].m_id);
|
|
rb.Read(si.m_params[i].m_value);
|
|
rb.Read(si.m_params[i].m_type);
|
|
}
|
|
|
|
bool isInResults = false;
|
|
for (AZStd::size_t i = 0; i < m_results.size(); ++i)
|
|
{
|
|
if (m_results[i].m_sessionId == si.m_sessionId)
|
|
{
|
|
isInResults = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!isInResults)
|
|
{
|
|
m_results.push_back(si);
|
|
}
|
|
|
|
if (m_results.size() == m_searchParams.m_maxSessions)
|
|
{
|
|
haveMaxResults = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check the timeout
|
|
timeElapsed = now - m_timeStart;
|
|
if (haveMaxResults || timeElapsed.count() > m_timeOutMS)
|
|
{
|
|
// we are done
|
|
SearchDone();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//=========================================================================
|
|
// AbortSearch
|
|
// [12/13/2010]
|
|
//=========================================================================
|
|
void
|
|
LANSearch::AbortSearch()
|
|
{
|
|
SearchDone();
|
|
}
|
|
|
|
//=========================================================================
|
|
// SearchDone
|
|
// [12/13/2010]
|
|
//=========================================================================
|
|
void
|
|
LANSearch::SearchDone()
|
|
{
|
|
m_isDone = true;
|
|
delete m_driver;
|
|
m_driver = nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// LANSessionService
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//=========================================================================
|
|
// LANSessionService
|
|
// [12/13/2010]
|
|
//=========================================================================
|
|
LANSessionService::LANSessionService(const SessionServiceDesc& desc)
|
|
: SessionService(desc)
|
|
{
|
|
#if AZ_TRAIT_OS_USE_WINDOWS_SOCKETS
|
|
WSAData wsaData;
|
|
int err = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
|
if (err != 0)
|
|
{
|
|
AZ_TracePrintf("GridMate", "Failed on WSAStartup with code %d\n", err);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
LANSessionService::~LANSessionService()
|
|
{
|
|
#if AZ_TRAIT_OS_USE_WINDOWS_SOCKETS
|
|
WSACleanup();
|
|
#endif
|
|
}
|
|
|
|
void LANSessionService::OnServiceRegistered(IGridMate* gridMate)
|
|
{
|
|
SessionService::OnServiceRegistered(gridMate);
|
|
|
|
ReplicaChunkDescriptorTable::Get().RegisterChunkType<LANSessionReplica, LANSessionReplica::LANSessionReplicaDesc>();
|
|
ReplicaChunkDescriptorTable::Get().RegisterChunkType<LANMember, LANMember::LANMemberDesc>();
|
|
ReplicaChunkDescriptorTable::Get().RegisterChunkType<LANMemberState>();
|
|
|
|
LANSessionServiceBus::Handler::BusConnect(gridMate);
|
|
|
|
EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnSessionServiceReady);
|
|
EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnSessionServiceReady);
|
|
}
|
|
|
|
void LANSessionService::OnServiceUnregistered(IGridMate* gridMate)
|
|
{
|
|
SessionService::OnServiceUnregistered(gridMate);
|
|
|
|
LANSessionServiceBus::Handler::BusDisconnect(gridMate);
|
|
}
|
|
|
|
GridSession* LANSessionService::HostSession(const LANSessionParams& params, const CarrierDesc& carrierDesc)
|
|
{
|
|
LANSession* session = aznew LANSession(this);
|
|
if (!session->Initialize(params, carrierDesc))
|
|
{
|
|
delete session;
|
|
return nullptr;
|
|
}
|
|
|
|
return session;
|
|
}
|
|
|
|
GridSession* LANSessionService::JoinSessionBySearchInfo(const LANSearchInfo& searchInfo, const JoinParams& params, const CarrierDesc& carrierDesc)
|
|
{
|
|
LANSession* session = aznew LANSession(this);
|
|
if (!session->Initialize(searchInfo, params, carrierDesc))
|
|
{
|
|
delete session;
|
|
return nullptr;
|
|
}
|
|
|
|
return session;
|
|
}
|
|
|
|
GridSession* LANSessionService::JoinSessionBySessionIdInfo(const SessionIdInfo* info, const JoinParams& params, const CarrierDesc& carrierDesc)
|
|
{
|
|
LANSearchInfo si;
|
|
si.m_sessionId = info->m_sessionId;
|
|
LANSession* session = aznew LANSession(this);
|
|
if (!session->Initialize(si, params, carrierDesc))
|
|
{
|
|
delete session;
|
|
return nullptr;
|
|
}
|
|
|
|
return session;
|
|
}
|
|
|
|
GridSearch* LANSessionService::StartGridSearch(const LANSearchParams& params)
|
|
{
|
|
return aznew LANSearch(params, this);
|
|
}
|