/*==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/NucleusLib/pnNetLog/pnNlCli.cpp * ***/ #include "Pch.h" #pragma hdrstop /***************************************************************************** * * Private * ***/ struct NetLogConn : SrvConn { NetAddress netaddr; ESrvType srvType; NetLogConn (const NetAddress & addr, ESrvType srvType); ~NetLogConn (); void Connect ( AsyncCancelId * cancelId, FAsyncNotifySocketProc notifyProc, void * param ); }; struct LogMsgTrans : SrvTrans { Srv2Log_LogMsg *msgBuffer; LogMsgTrans(); ~LogMsgTrans(); bool OnTransReply ( ENetError * error, SrvMsgHeader * msg ); }; struct LogConnEventNode { LINK(LogConnEventNode) link; Srv2Log_LogMsg * msg; unsigned sendTimeMs; LogConnEventNode(Srv2Log_LogMsg *msg, unsigned timeStampMs); }; /***************************************************************************** * * Private Data * ***/ static NetLogConn * s_conn; static CCritSect s_critsect; static IniChangeReg * s_change; static ESrvType s_srvType; static LISTDECL(LogConnEventNode, link) s_eventQueue; static AsyncTimer * s_timer; static long s_perf[kNlCliNumPerf]; static bool s_running; static const unsigned kIssueSaveMs = 100; static const unsigned kMaxNumberOfTransactions = 2000; /***************************************************************************** * * Private Functions * ***/ //============================================================================ static NetLogConn * GetConnIncRef () { NetLogConn * conn; s_critsect.Enter(); { if (nil != (conn = s_conn)) conn->IncRef(); } s_critsect.Leave(); return conn; } //============================================================================ static void NetLogConnConnect (NetAddress & addr, ESrvType srvType) { NetLogConn *conn = SRV_CONN_ALLOC(NetLogConn)(addr, srvType); s_critsect.Enter(); { SWAP(s_conn, conn); } s_critsect.Leave(); if (conn) conn->Destroy(); } //============================================================================ static void NetLogConnDisconnect () { NetLogConn *conn = nil; s_critsect.Enter(); { SWAP(s_conn, conn); } s_critsect.Leave(); if (conn) conn->Destroy(); } //============================================================================ static void AddEventNode (Srv2Log_LogMsg *msg) { LogConnEventNode *node = NEW(LogConnEventNode)(msg, TimeGetMs() + kIssueSaveMs); s_critsect.Enter(); { s_eventQueue.Link(node); } s_critsect.Leave(); AsyncTimerUpdate( s_timer, kIssueSaveMs, kAsyncTimerUpdateSetPriorityHigher ); } //============================================================================ static void ParseIni (Ini * ini) { unsigned iter; const IniValue * value = IniGetFirstValue( ini, L"Server Locations", L"LogSrv", &iter ); if (value) { wchar addrStr[32]; IniGetString(value, addrStr, arrsize(addrStr), 0, nil); NetAddress addr; NetAddressFromString(&addr, addrStr, kNetDefaultServerPort); NetLogConnConnect(addr, s_srvType); } } //============================================================================ static void IniChangeCallback (const wchar fullPath[]) { Ini * ini = IniOpen(fullPath); ParseIni(ini); IniClose(ini); } //============================================================================ static unsigned TimerCallback (void *) { LISTDECL(LogConnEventNode, link) sendList; unsigned sleepMs = kAsyncTimeInfinite; s_critsect.Enter(); { int allowedNumTrans = kMaxNumberOfTransactions - s_perf[kNlCliNumPendingSaves]; // find the number of transactions that we can send based on the number in transit, and the max number allowed if(allowedNumTrans < 0) // this could be negative, if so set to zero allowedNumTrans = 0; dword currTime = TimeGetMs(); LogConnEventNode *hash; int timeDiff; // Add pending saves, up to the max allowed per update for(;;) { if(!allowedNumTrans && s_running) { sleepMs = 5000; // we are at our max number of transactions sleep for 5 seconds and try again break; } hash = s_eventQueue.Head(); if(!hash) break; // no messages left. We will wait until another message comes in before another timer update timeDiff = hash->sendTimeMs - currTime; // nodes are naturally ordered by increasing sendTimeMs if(!s_running || (timeDiff <= 0)) { sendList.Link(hash); --allowedNumTrans; } else { sleepMs = timeDiff; break; } } } s_critsect.Leave(); while(LogConnEventNode *node = sendList.Head()) { LogMsgTrans * trans = SRV_TRANS_ALLOC(LogMsgTrans); trans->msgBuffer = node->msg; if (NetLogConn * conn = GetConnIncRef()) { conn->SendRequest(trans, node->msg); conn->DecRef(); } else { trans->TransCancel(kNetErrTimeout); } delete node; } return sleepMs; } //============================================================================ static unsigned CalcArgsLength (const NetLogEvent &event, va_list args) { unsigned length = 0; unsigned paramType = kNumLogParamTypes; // invalidate unsigned field = 0; for(unsigned i = 0; i < event.numFields * 2; ++i) { if(!(i % 2)) { paramType = va_arg(args, unsigned); continue; } //validate parameter type if(paramType != (unsigned)event.fields[field].type) { length = 0; LogMsg( kLogError, "Log parameter types do not match for event: %s parameter: %s?", event.eventName, event.fields[field].name); break; } switch(event.fields[field].type) { case kLogParamInt: { va_arg(args, int); length += sizeof(int); } break; case kLogParamUnsigned: { va_arg(args, unsigned); length += sizeof(unsigned); } break; case kLogParamFloat: { va_arg(args, float); length += sizeof(float); } break; case kLogParamLong: { va_arg(args, long); length += sizeof(long); } break; case kLogParamLongLong: { va_arg(args, long long); length += sizeof(long long); } break; case kLogParamUuid: { va_arg(args, Uuid); length += sizeof(Uuid); } break; case kLogParamStringW: { wchar *str = va_arg(args, wchar *); if(!str) str = L""; length += StrBytes(str); } break; default: hsAssert(false, "Unknown argument type in log statement"); return 0; } ++field; } return length; } /***************************************************************************** * * NetLogConn * ***/ //============================================================================ NetLogConn::NetLogConn (const NetAddress & addr, ESrvType srvType) : netaddr(addr), srvType(srvType) { AtomicAdd(&s_perf[kNlCliNumConn], 1); SetAutoPing(); AutoReconnect(); } //============================================================================ NetLogConn::~NetLogConn () { AtomicAdd(&s_perf[kNlCliNumConn], -1); } //============================================================================ void NetLogConn::Connect ( AsyncCancelId * cancelId, FAsyncNotifySocketProc notifyProc, void * param ) { // Connect to remote server Srv2Log_Connect connect; connect.hdr.connType = kConnTypeSrvToLog; 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); connect.data.buildId = BuildId(); connect.data.srvType = srvType; connect.data.buildType = BuildType(); connect.data.productId = ProductId(); AsyncSocketConnect( cancelId, netaddr, notifyProc, param, &connect, sizeof(connect) ); } /***************************************************************************** * * LogMsgTrans * ***/ //============================================================================ LogMsgTrans::LogMsgTrans () : msgBuffer(nil) { AtomicAdd(&s_perf[kNlCliNumTrans], 1); } //============================================================================ LogMsgTrans::~LogMsgTrans () { AtomicAdd(&s_perf[kNlCliNumTrans], -1); } //============================================================================ bool LogMsgTrans::OnTransReply ( ENetError * error, SrvMsgHeader * msg ) { ref(msg); bool result; if (msg->protocolId != kNetProtocolSrv2Log) { result = SrvTrans::OnTransReply(error, msg); } else { result = true; } if(IS_NET_ERROR(*error) && s_running) { AddEventNode(msgBuffer); } else { FREE(msgBuffer); } return true; } /***************************************************************************** * * LogConnEventNode * ***/ //============================================================================ LogConnEventNode::LogConnEventNode (Srv2Log_LogMsg *msg, unsigned sendTimeMs) : msg(msg), sendTimeMs(sendTimeMs) { } /***************************************************************************** * * Protected * ***/ //============================================================================ void NetLogCliInitialize (ESrvType srvType) { s_running = true; s_srvType = srvType; AsyncTimerCreate( &s_timer, TimerCallback, kAsyncTimeInfinite, nil ); IniChangeAdd(L"plServer", IniChangeCallback, &s_change); } //============================================================================ void NetLogCliShutdown () { s_running = false; if(s_change) { IniChangeRemove(s_change, true); s_change = false; } if(s_timer) { AsyncTimerDeleteCallback(s_timer, TimerCallback); s_timer = nil; } NetLogConnDisconnect(); } //============================================================================ void NetLogCliDestroy () { while(s_perf[kNlCliNumTrans]) AsyncSleep(10); while(s_perf[kNlCliNumConn]) AsyncSleep(10); } //============================================================================ void NetLogCliSendEvent (const NetLogEvent &event, va_list args) { Srv2Log_LogMsg *msg; unsigned length = CalcArgsLength(event, args); if(!length) return; CSrvPackBuffer pack( sizeof(*msg) + length ); msg = (Srv2Log_LogMsg *) pack.Alloc(sizeof(*msg)); msg->transId = 0; msg->protocolId = kNetProtocolSrv2Log; msg->messageId = kSrv2Log_LogMsg; msg->eventType = event.logEventType; msg->timestamp = TimeGetLocalTime(); unsigned field = 0; unsigned paramType = kNumLogParamTypes; // if we get here the template parameters have already been validated by CalcLength, no need to do that again. for(unsigned i = 0; i < event.numFields * 2; ++i) { if(!(i % 2)) { paramType = va_arg(args, unsigned); continue; } switch(event.fields[field].type) { case kLogParamInt: { int i = va_arg(args, int); pack.AddData(&i, sizeof(int)); } break; case kLogParamUnsigned: { unsigned u = va_arg(args, unsigned); pack.AddData(&u, sizeof(unsigned)); } break; case kLogParamFloat: { float f = va_arg(args, float); pack.AddData(&f, sizeof(float)); } break; case kLogParamLong: { long l = va_arg(args, long); pack.AddData(&l, sizeof(long)); } break; case kLogParamLongLong: { long long ll = va_arg(args, long long); pack.AddData(&ll, sizeof(long long)); } break; case kLogParamUuid: { Uuid uuid = va_arg(args, Uuid); pack.AddData(&uuid, sizeof(Uuid)); } break; case kLogParamStringW: { wchar *str = va_arg(args, wchar *); if(!str) str = L""; pack.AddString(str); } break; } ++field; } msg->messageBytes = pack.Size(); AddEventNode(msg); }