/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ /***************************************************************************** * * $/Plasma20/Sources/Plasma/PubUtilLib/plNetGameLib/Private/plNglGame.cpp * ***/ #include "../Pch.h" #pragma hdrstop // This file excluded from pre-compiled header because it is auto-generated by the build server. #include "pnNetBase/pnNbGameKey.hpp" namespace Ngl { namespace Game { /***************************************************************************** * * Private * ***/ struct CliGmConn : AtomicRef { LINK(CliGmConn) link; CCritSect critsect; AsyncSocket sock; AsyncCancelId cancelId; NetCli * cli; NetAddress addr; unsigned seq; bool abandoned; // ping AsyncTimer * pingTimer; unsigned pingSendTimeMs; unsigned lastHeardTimeMs; CliGmConn (); ~CliGmConn (); void Send (const unsigned_ptr fields[], unsigned count); }; //============================================================================ // JoinAgeRequestTrans //============================================================================ struct JoinAgeRequestTrans : NetGameTrans { FNetCliGameJoinAgeRequestCallback m_callback; void * m_param; // sent unsigned m_ageMcpId; Uuid m_accountUuid; unsigned m_playerInt; JoinAgeRequestTrans ( unsigned ageMcpId, const Uuid & accountUuid, unsigned playerInt, FNetCliGameJoinAgeRequestCallback callback, void * param ); bool Send (); void Post (); bool Recv ( const byte msg[], unsigned bytes ); }; //============================================================================ // RcvdPropagatedBufferTrans //============================================================================ struct RcvdPropagatedBufferTrans : NetNotifyTrans { unsigned bufferType; unsigned bufferBytes; byte * bufferData; RcvdPropagatedBufferTrans () : NetNotifyTrans(kGmRcvdPropagatedBufferTrans) {} ~RcvdPropagatedBufferTrans (); void Post (); }; //============================================================================ // RcvdGameMgrMsgTrans //============================================================================ struct RcvdGameMgrMsgTrans : NetNotifyTrans { unsigned bufferBytes; byte * bufferData; RcvdGameMgrMsgTrans () : NetNotifyTrans(kGmRcvdGameMgrMsgTrans) {} ~RcvdGameMgrMsgTrans (); void Post (); }; /***************************************************************************** * * Private data * ***/ enum { kPerfConnCount, kPingDisabled, kNumPerf }; static bool s_running; static CCritSect s_critsect; static LISTDECL(CliGmConn, link) s_conns; static CliGmConn * s_active; static FNetCliGameRecvBufferHandler s_bufHandler; static FNetCliGameRecvGameMgrMsgHandler s_gameMgrMsgHandler; static long s_perf[kNumPerf]; /***************************************************************************** * * Internal functions * ***/ //=========================================================================== static unsigned GetNonZeroTimeMs () { if (unsigned ms = TimeGetMs()) return ms; return 1; } //============================================================================ static CliGmConn * GetConnIncRef_CS (const char tag[]) { if (CliGmConn * conn = s_active) if (conn->cli) { conn->IncRef(tag); return conn; } return nil; } //============================================================================ static CliGmConn * GetConnIncRef (const char tag[]) { CliGmConn * conn; s_critsect.Enter(); { conn = GetConnIncRef_CS(tag); } s_critsect.Leave(); return conn; } //============================================================================ static void UnlinkAndAbandonConn_CS (CliGmConn * conn) { s_conns.Unlink(conn); conn->abandoned = true; if (conn->cancelId) { AsyncSocketConnectCancel(nil, conn->cancelId); conn->cancelId = 0; } else if (conn->sock) { AsyncSocketDisconnect(conn->sock, true); } else { conn->DecRef("Lifetime"); } } //============================================================================ static bool ConnEncrypt (ENetError error, void * param) { CliGmConn * conn = (CliGmConn *) param; if (IS_NET_SUCCESS(error)) { s_critsect.Enter(); { SWAP(s_active, conn); } s_critsect.Leave(); } return IS_NET_SUCCESS(error); } //============================================================================ static void NotifyConnSocketConnect (CliGmConn * conn) { conn->TransferRef("Connecting", "Connected"); conn->cli = NetCliConnectAccept( conn->sock, kNetProtocolCli2Game, true, ConnEncrypt, 0, nil, conn ); } //============================================================================ static void NotifyConnSocketConnectFailed (CliGmConn * conn) { bool notify; s_critsect.Enter(); { conn->cancelId = 0; s_conns.Unlink(conn); notify = s_running && !conn->abandoned && (!s_active || conn == s_active); if (conn == s_active) s_active = nil; } s_critsect.Leave(); NetTransCancelByConnId(conn->seq, kNetErrTimeout); conn->DecRef("Connecting"); conn->DecRef("Lifetime"); if (notify) ReportNetError(kNetProtocolCli2Game, kNetErrConnectFailed); } //============================================================================ static void NotifyConnSocketDisconnect (CliGmConn * conn) { bool notify; s_critsect.Enter(); { s_conns.Unlink(conn); notify = s_running && !conn->abandoned && (!s_active || conn == s_active); if (conn == s_active) s_active = nil; } s_critsect.Leave(); // Cancel all transactions in process on this connection. NetTransCancelByConnId(conn->seq, kNetErrTimeout); conn->DecRef("Connected"); conn->DecRef("Lifetime"); if (notify) ReportNetError(kNetProtocolCli2Game, kNetErrDisconnected); } //============================================================================ static bool NotifyConnSocketRead (CliGmConn * conn, AsyncNotifySocketRead * read) { // TODO: Only dispatch messages from the active game server conn->lastHeardTimeMs = GetNonZeroTimeMs(); bool result = NetCliDispatch(conn->cli, read->buffer, read->bytes, nil); read->bytesProcessed += read->bytes; return result; } //============================================================================ static bool SocketNotifyCallback ( AsyncSocket sock, EAsyncNotifySocket code, AsyncNotifySocket * notify, void ** userState ) { bool result = true; CliGmConn * conn; switch (code) { case kNotifySocketConnectSuccess: conn = (CliGmConn *) notify->param; *userState = conn; conn->TransferRef("Connecting", "Connected"); bool abandoned; s_critsect.Enter(); { conn->sock = sock; conn->cancelId = 0; abandoned = conn->abandoned; } s_critsect.Leave(); if (abandoned) AsyncSocketDisconnect(sock, true); else NotifyConnSocketConnect(conn); break; case kNotifySocketConnectFailed: conn = (CliGmConn *) notify->param; NotifyConnSocketConnectFailed(conn); break; case kNotifySocketDisconnect: conn = (CliGmConn *) *userState; NotifyConnSocketDisconnect(conn); break; case kNotifySocketRead: conn = (CliGmConn *) *userState; result = NotifyConnSocketRead(conn, (AsyncNotifySocketRead *) notify); break; } return result; } //============================================================================ static void Connect ( const NetAddress & addr ) { CliGmConn * conn = NEWZERO(CliGmConn); conn->addr = addr; conn->seq = ConnNextSequence(); conn->lastHeardTimeMs = GetNonZeroTimeMs(); conn->IncRef("Lifetime"); conn->IncRef("Connecting"); s_critsect.Enter(); { while (CliGmConn * conn = s_conns.Head()) UnlinkAndAbandonConn_CS(conn); s_conns.Link(conn); } s_critsect.Leave(); Cli2Game_Connect connect; connect.hdr.connType = kConnTypeCliToGame; connect.hdr.hdrBytes = sizeof(connect.hdr); connect.hdr.buildId = BuildId(); connect.hdr.buildType = BuildType(); connect.hdr.branchId = BranchId(); connect.hdr.productId = ProductId(); connect.data.dataBytes = sizeof(connect.data); AsyncSocketConnect( &conn->cancelId, addr, SocketNotifyCallback, conn, &connect, sizeof(connect), 0, 0 ); } /***************************************************************************** * * CliGmConn * ***/ //============================================================================ CliGmConn::CliGmConn () { AtomicAdd(&s_perf[kPerfConnCount], 1); } //============================================================================ CliGmConn::~CliGmConn () { if (cli) NetCliDelete(cli, true); AtomicAdd(&s_perf[kPerfConnCount], -1); } //============================================================================ void CliGmConn::Send (const unsigned_ptr fields[], unsigned count) { critsect.Enter(); { NetCliSend(cli, fields, count); NetCliFlush(cli); } critsect.Leave(); } /***************************************************************************** * * Cli2Game protocol * ***/ //============================================================================ static bool Recv_PingReply ( const byte msg[], unsigned bytes, void * param ) { ref(msg); ref(bytes); ref(param); return true; } //============================================================================ static bool Recv_JoinAgeReply ( const byte msg[], unsigned bytes, void * param ) { ref(bytes); ref(param); const Game2Cli_JoinAgeReply & reply = *(const Game2Cli_JoinAgeReply *)msg; if (sizeof(reply) != bytes) return false; NetTransRecv(reply.transId, msg, bytes); return true; } //============================================================================ static bool Recv_PropagateBuffer ( const byte msg[], unsigned bytes, void * param ) { ref(bytes); ref(param); const Game2Cli_PropagateBuffer & reply = *(const Game2Cli_PropagateBuffer *)msg; RcvdPropagatedBufferTrans * trans = NEW(RcvdPropagatedBufferTrans); trans->bufferType = reply.type; trans->bufferBytes = reply.bytes; trans->bufferData = (byte *)ALLOC(reply.bytes); MemCopy(trans->bufferData, reply.buffer, reply.bytes); NetTransSend(trans); return true; } //============================================================================ static bool Recv_GameMgrMsg ( const byte msg[], unsigned bytes, void * param ) { ref(bytes); ref(param); const Game2Cli_GameMgrMsg & reply = *(const Game2Cli_GameMgrMsg *)msg; RcvdGameMgrMsgTrans * trans = NEW(RcvdGameMgrMsgTrans); trans->bufferBytes = reply.bytes; trans->bufferData = (byte *)ALLOC(reply.bytes); MemCopy(trans->bufferData, reply.buffer, reply.bytes); NetTransSend(trans); return true; } //============================================================================ // Send/Recv protocol handler init //============================================================================ #define MSG(s) kNetMsg_Cli2Game_##s static NetMsgInitSend s_send[] = { { MSG(PingRequest), }, { MSG(JoinAgeRequest), }, { MSG(PropagateBuffer), }, { MSG(GameMgrMsg), }, }; #undef MSG #define MSG(s) kNetMsg_Game2Cli_##s, Recv_##s static NetMsgInitRecv s_recv[] = { { MSG(PingReply) }, { MSG(JoinAgeReply), }, { MSG(PropagateBuffer), }, { MSG(GameMgrMsg), }, }; #undef MSG /***************************************************************************** * * JoinAgeRequestTrans * ***/ //============================================================================ JoinAgeRequestTrans::JoinAgeRequestTrans ( unsigned ageMcpId, const Uuid & accountUuid, unsigned playerInt, FNetCliGameJoinAgeRequestCallback callback, void * param ) : NetGameTrans(kJoinAgeRequestTrans) , m_ageMcpId(ageMcpId) , m_accountUuid(accountUuid) , m_playerInt(playerInt) , m_callback(callback) , m_param(param) { } //============================================================================ bool JoinAgeRequestTrans::Send () { if (!AcquireConn()) return false; const unsigned_ptr msg[] = { kCli2Game_JoinAgeRequest, m_transId, m_ageMcpId, (unsigned_ptr) &m_accountUuid, m_playerInt, }; m_conn->Send(msg, arrsize(msg)); return true; } //============================================================================ void JoinAgeRequestTrans::Post () { m_callback( m_result, m_param ); } //============================================================================ bool JoinAgeRequestTrans::Recv ( const byte msg[], unsigned bytes ) { ref(bytes); const Game2Cli_JoinAgeReply & reply = *(const Game2Cli_JoinAgeReply *) msg; m_result = reply.result; m_state = kTransStateComplete; return true; } /***************************************************************************** * * RcvdPropagatedBufferTrans * ***/ //============================================================================ RcvdPropagatedBufferTrans::~RcvdPropagatedBufferTrans () { FREE(bufferData); } //============================================================================ void RcvdPropagatedBufferTrans::Post () { if (s_bufHandler) s_bufHandler(bufferType, bufferBytes, bufferData); } /***************************************************************************** * * RcvdGameMgrMsgTrans * ***/ //============================================================================ RcvdGameMgrMsgTrans::~RcvdGameMgrMsgTrans () { FREE(bufferData); } //============================================================================ void RcvdGameMgrMsgTrans::Post () { if (s_gameMgrMsgHandler) s_gameMgrMsgHandler((GameMsgHeader *)bufferData); } } using namespace Game; /***************************************************************************** * * NetGameTrans * ***/ //============================================================================ NetGameTrans::NetGameTrans (ETransType transType) : NetTrans(kNetProtocolCli2Game, transType) , m_conn(nil) { } //============================================================================ NetGameTrans::~NetGameTrans () { ReleaseConn(); } //============================================================================ bool NetGameTrans::AcquireConn () { if (!m_conn) m_conn = GetConnIncRef("AcquireConn"); return m_conn != nil; } //============================================================================ void NetGameTrans::ReleaseConn () { if (m_conn) { m_conn->DecRef("AcquireConn"); m_conn = nil; } } /***************************************************************************** * * Protected functions * ***/ //============================================================================ void GameInitialize () { s_running = true; NetMsgProtocolRegister( kNetProtocolCli2Game, false, s_send, arrsize(s_send), s_recv, arrsize(s_recv), kDhGValue, BigNum(sizeof(kDhXData), kDhXData), BigNum(sizeof(kDhNData), kDhNData) ); } //============================================================================ void GameDestroy (bool wait) { s_running = false; s_bufHandler = nil; s_gameMgrMsgHandler = nil; NetTransCancelByProtocol( kNetProtocolCli2Game, kNetErrRemoteShutdown ); NetMsgProtocolDestroy( kNetProtocolCli2Game, false ); s_critsect.Enter(); { while (CliGmConn * conn = s_conns.Head()) UnlinkAndAbandonConn_CS(conn); s_active = nil; } s_critsect.Leave(); if (!wait) return; while (s_perf[kPerfConnCount]) { NetTransUpdate(); AsyncSleep(10); } } //============================================================================ bool GameQueryConnected () { bool result; s_critsect.Enter(); { if (nil != (result = s_active)) result &= (nil != s_active->cli); } s_critsect.Leave(); return result; } //============================================================================ unsigned GameGetConnId () { unsigned connId; s_critsect.Enter(); connId = (s_active) ? s_active->seq : 0; s_critsect.Leave(); return connId; } //============================================================================ void GamePingEnable (bool enable) { s_perf[kPingDisabled] = !enable; } } using namespace Ngl; /***************************************************************************** * * Exported functions * ***/ //============================================================================ void NetCliGameStartConnect ( const NetAddressNode & node ) { NetAddress addr; NetAddressFromNode(node, kNetDefaultClientPort, &addr); Connect(addr); } //============================================================================ void NetCliGameDisconnect () { s_critsect.Enter(); { while (CliGmConn * conn = s_conns.Head()) UnlinkAndAbandonConn_CS(conn); s_active = nil; } s_critsect.Leave(); } //============================================================================ void NetCliGameJoinAgeRequest ( unsigned ageMcpId, const Uuid & accountUuid, unsigned playerInt, FNetCliGameJoinAgeRequestCallback callback, void * param ) { JoinAgeRequestTrans * trans = NEWZERO(JoinAgeRequestTrans)( ageMcpId, accountUuid, playerInt, callback, param ); NetTransSend(trans); } //============================================================================ void NetCliGameSetRecvBufferHandler ( FNetCliGameRecvBufferHandler handler ) { s_bufHandler = handler; } //============================================================================ void NetCliGamePropagateBuffer ( unsigned type, unsigned bytes, const byte buffer[] ) { CliGmConn * conn = GetConnIncRef("PropBuffer"); if (!conn) return; const unsigned_ptr msg[] = { kCli2Game_PropagateBuffer, type, bytes, (unsigned_ptr) buffer, }; conn->Send(msg, arrsize(msg)); conn->DecRef("PropBuffer"); } //============================================================================ void NetCliGameSetRecvGameMgrMsgHandler (FNetCliGameRecvGameMgrMsgHandler handler) { s_gameMgrMsgHandler = handler; } //============================================================================ void NetCliGameSendGameMgrMsg (GameMsgHeader * msgHdr) { CliGmConn * conn = GetConnIncRef("GameMgrMsg"); if (!conn) return; const unsigned_ptr msg[] = { kCli2Game_GameMgrMsg, msgHdr->messageBytes, (unsigned_ptr) msgHdr, }; conn->Send(msg, arrsize(msg)); conn->DecRef("GameMgrMsg"); }