Thread safe saving of score

This commit is contained in:
Zwelf 2020-06-08 17:11:41 +02:00
parent 5dcb6f3b0d
commit 13c80fdc56
4 changed files with 132 additions and 67 deletions

View file

@ -123,6 +123,7 @@ void CPlayer::Reset()
#if defined(CONF_SQL) #if defined(CONF_SQL)
m_LastSQLQuery = 0; m_LastSQLQuery = 0;
m_SqlQueryResult = nullptr; m_SqlQueryResult = nullptr;
m_SqlFinishResult = nullptr;
#endif #endif
int64 Now = Server()->Tick(); int64 Now = Server()->Tick();
@ -148,8 +149,18 @@ void CPlayer::Tick()
if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS-g_Config.m_DbgDummies) if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS-g_Config.m_DbgDummies)
#endif #endif
#if defined(CONF_SQL) #if defined(CONF_SQL)
ProcessSqlResult(); if(m_SqlQueryResult != nullptr && m_SqlQueryResult.use_count() == 1)
{
ProcessSqlResult(*m_SqlQueryResult);
m_SqlQueryResult = nullptr;
}
if(m_SqlFinishResult != nullptr && m_SqlFinishResult.use_count() == 1)
{
ProcessSqlResult(*m_SqlFinishResult);
m_SqlFinishResult = nullptr;
}
#endif #endif
if(!Server()->ClientIngame(m_ClientID)) if(!Server()->ClientIngame(m_ClientID))
return; return;
@ -779,45 +790,45 @@ void CPlayer::SpectatePlayerName(const char *pName)
} }
#if defined(CONF_SQL) #if defined(CONF_SQL)
void CPlayer::ProcessSqlResult() void CPlayer::ProcessSqlResult(CSqlPlayerResult &Result)
{ {
if(m_SqlQueryResult == nullptr || m_SqlQueryResult.use_count() != 1) if(Result.m_Done) // SQL request was successful
return;
if(m_SqlQueryResult->m_Done) // SQL request was successful
{ {
int NumMessages = (int)(sizeof(m_SqlQueryResult->m_aaMessages)/sizeof(m_SqlQueryResult->m_aaMessages[0])); int NumMessages = (int)(sizeof(Result.m_aaMessages)/sizeof(Result.m_aaMessages[0]));
switch(m_SqlQueryResult->m_MessageKind) switch(Result.m_MessageKind)
{ {
case CSqlPlayerResult::DIRECT: case CSqlPlayerResult::DIRECT:
for(int i = 0; i < NumMessages; i++) for(int i = 0; i < NumMessages; i++)
{ {
if(m_SqlQueryResult->m_aaMessages[i][0] == 0) if(Result.m_aaMessages[i][0] == 0)
break; break;
GameServer()->SendChatTarget(m_ClientID, m_SqlQueryResult->m_aaMessages[i]); GameServer()->SendChatTarget(m_ClientID, Result.m_aaMessages[i]);
} }
break; break;
case CSqlPlayerResult::ALL: case CSqlPlayerResult::ALL:
for(int i = 0; i < NumMessages; i++) for(int i = 0; i < NumMessages; i++)
{ {
if(m_SqlQueryResult->m_aaMessages[i][0] == 0) if(Result.m_aaMessages[i][0] == 0)
break; break;
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, m_SqlQueryResult->m_aaMessages[i]); GameServer()->SendChat(-1, CGameContext::CHAT_ALL, Result.m_aaMessages[i]);
} }
break; break;
case CSqlPlayerResult::BROADCAST:
if(Result.m_Data.m_Broadcast[0] != 0)
GameServer()->SendBroadcast(Result.m_Data.m_Broadcast, -1);
break;
case CSqlPlayerResult::MAP_VOTE: case CSqlPlayerResult::MAP_VOTE:
// TODO: start vote // TODO: start vote
break; break;
case CSqlPlayerResult::PLAYER_INFO: case CSqlPlayerResult::PLAYER_INFO:
GameServer()->Score()->PlayerData(m_ClientID)->Set( GameServer()->Score()->PlayerData(m_ClientID)->Set(
m_SqlQueryResult->m_Data.m_Info.m_Time, Result.m_Data.m_Info.m_Time,
m_SqlQueryResult->m_Data.m_Info.m_CpTime Result.m_Data.m_Info.m_CpTime
); );
printf("%d", m_SqlQueryResult->m_Data.m_Info.m_Score); m_Score = Result.m_Data.m_Info.m_Score;
m_Score = m_SqlQueryResult->m_Data.m_Info.m_Score; m_HasFinishScore = Result.m_Data.m_Info.m_HasFinishScore;
m_HasFinishScore = m_SqlQueryResult->m_Data.m_Info.m_HasFinishScore;
Server()->ExpireServerInfo(); Server()->ExpireServerInfo();
int Birthday = m_SqlQueryResult->m_Data.m_Info.m_Birthday; int Birthday = Result.m_Data.m_Info.m_Birthday;
if(Birthday != 0) if(Birthday != 0)
{ {
char aBuf[512]; char aBuf[512];
@ -833,6 +844,5 @@ void CPlayer::ProcessSqlResult()
break; break;
} }
} }
m_SqlQueryResult = nullptr;
} }
#endif #endif

View file

@ -198,9 +198,10 @@ public:
bool m_Halloween; bool m_Halloween;
bool m_FirstPacket; bool m_FirstPacket;
#if defined(CONF_SQL) #if defined(CONF_SQL)
void ProcessSqlResult(); void ProcessSqlResult(CSqlPlayerResult &Result);
int64 m_LastSQLQuery; int64 m_LastSQLQuery;
std::shared_ptr<CSqlPlayerResult> m_SqlQueryResult; std::shared_ptr<CSqlPlayerResult> m_SqlQueryResult;
std::shared_ptr<CSqlPlayerResult> m_SqlFinishResult;
#endif #endif
bool m_NotEligibleForFinish; bool m_NotEligibleForFinish;
int64 m_EligibleForFinishCheck; int64 m_EligibleForFinishCheck;

View file

@ -20,7 +20,7 @@
std::atomic_int CSqlScore::ms_InstanceCount(0); std::atomic_int CSqlScore::ms_InstanceCount(0);
CSqlPlayerResult::CSqlPlayerResult() : CSqlPlayerResult::CSqlPlayerResult() :
m_Done(0) m_Done(false)
{ {
SetVariant(Variant::DIRECT); SetVariant(Variant::DIRECT);
} }
@ -35,6 +35,9 @@ void CSqlPlayerResult::SetVariant(Variant v)
for(int i = 0; i < (int)(sizeof(m_aaMessages)/sizeof(m_aaMessages[0])); i++) for(int i = 0; i < (int)(sizeof(m_aaMessages)/sizeof(m_aaMessages[0])); i++)
m_aaMessages[i][0] = 0; m_aaMessages[i][0] = 0;
break; break;
case BROADCAST:
m_Data.m_Broadcast[0] = 0;
break;
case MAP_VOTE: case MAP_VOTE:
break; break;
case PLAYER_INFO: case PLAYER_INFO:
@ -476,14 +479,20 @@ bool CSqlScore::MapInfoThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerR
void CSqlScore::SaveScore(int ClientID, float Time, const char *pTimestamp, float CpTime[NUM_CHECKPOINTS], bool NotEligible) void CSqlScore::SaveScore(int ClientID, float Time, const char *pTimestamp, float CpTime[NUM_CHECKPOINTS], bool NotEligible)
{ {
CConsole* pCon = (CConsole*)GameServer()->Console(); CConsole* pCon = (CConsole*)GameServer()->Console();
if(pCon->m_Cheated) if(pCon->m_Cheated || NotEligible)
return; return;
CSqlScoreData *Tmp = new CSqlScoreData(nullptr);
CPlayer *pCurPlayer = GameServer()->m_apPlayers[ClientID];
if(pCurPlayer->m_SqlFinishResult != nullptr)
dbg_msg("sql", "WARNING: previous save score result didn't complete, overwriting it now");
pCurPlayer->m_SqlFinishResult = std::make_shared<CSqlPlayerResult>();
CSqlScoreData *Tmp = new CSqlScoreData(pCurPlayer->m_SqlFinishResult);
Tmp->m_Map = g_Config.m_SvMap;
FormatUuid(GameServer()->GameUuid(), Tmp->m_GameUuid, sizeof(Tmp->m_GameUuid));
Tmp->m_ClientID = ClientID; Tmp->m_ClientID = ClientID;
Tmp->m_Name = Server()->ClientName(ClientID); Tmp->m_Name = Server()->ClientName(ClientID);
Tmp->m_Time = Time; Tmp->m_Time = Time;
str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp)); str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp));
Tmp->m_NotEligible = NotEligible;
for(int i = 0; i < NUM_CHECKPOINTS; i++) for(int i = 0; i < NUM_CHECKPOINTS; i++)
Tmp->m_aCpCurrent[i] = CpTime[i]; Tmp->m_aCpCurrent[i] = CpTime[i];
@ -494,8 +503,8 @@ void CSqlScore::SaveScore(int ClientID, float Time, const char *pTimestamp, floa
bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure) bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure)
{ {
/*
const CSqlScoreData *pData = dynamic_cast<const CSqlScoreData *>(pGameData); const CSqlScoreData *pData = dynamic_cast<const CSqlScoreData *>(pGameData);
auto paMessages = pData->m_pResult->m_aaMessages;
if(HandleFailure) if(HandleFailure)
{ {
@ -504,38 +513,60 @@ bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlaye
lock_wait(ms_FailureFileLock); lock_wait(ms_FailureFileLock);
IOHANDLE File = io_open(g_Config.m_SvSqlFailureFile, IOFLAG_APPEND); IOHANDLE File = io_open(g_Config.m_SvSqlFailureFile, IOFLAG_APPEND);
if(File) if(File == 0)
{ {
lock_unlock(ms_FailureFileLock);
dbg_msg("sql", "ERROR: Could not save Score, NOT even to a file");
return false;
}
dbg_msg("sql", "ERROR: Could not save Score, writing insert to a file now..."); dbg_msg("sql", "ERROR: Could not save Score, writing insert to a file now...");
char aBuf[768]; char aBuf[1024];
str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %%s_race(Map, Name, Timestamp, Time, Server, cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, GameID, DDNet7) VALUES ('%s', '%s', '%s', '%.2f', '%s', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%s', false);", pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), pData->m_aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName, pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], pData->m_aCpCurrent[24], pData->m_GameUuid.ClrStr()); str_format(aBuf, sizeof(aBuf),
"INSERT IGNORE INTO %%s_race(Map, Name, Timestamp, Time, Server, "
"cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, "
"cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, "
"GameID, DDNet7) "
"VALUES ('%s', '%s', '%s', '%.2f', '%s',"
"'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', "
"'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', "
"'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', "
"'%.2f', '%s', false);",
pData->m_Map.ClrStr(), pData->m_Name.ClrStr(),
pData->m_aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName,
pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2],
pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5],
pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8],
pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11],
pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14],
pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17],
pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20],
pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23],
pData->m_aCpCurrent[24],
pData->m_GameUuid);
io_write(File, aBuf, str_length(aBuf)); io_write(File, aBuf, str_length(aBuf));
io_write_newline(File); io_write_newline(File);
io_close(File); io_close(File);
lock_unlock(ms_FailureFileLock); lock_unlock(ms_FailureFileLock);
pData->GameServer()->SendBroadcast("Database connection failed, score written to a file instead. Admins will add it manually in a few days.", -1); pData->m_pResult->SetVariant(CSqlPlayerResult::BROADCAST);
strcpy(pData->m_pResult->m_Data.m_Broadcast,
"Database connection failed, score written to a file instead. Admins will add it manually in a few days.");
pData->m_pResult->m_Done = true;
return true; return true;
} }
lock_unlock(ms_FailureFileLock);
dbg_msg("sql", "ERROR: Could not save Score, NOT even to a file");
return false;
}
if(pData->m_NotEligible)
{
return false;
}
try try
{ {
char aBuf[768]; char aBuf[1024];
str_format(aBuf, sizeof(aBuf), "SELECT * FROM %s_race WHERE Map='%s' AND Name='%s' ORDER BY time ASC LIMIT 1;", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr()); str_format(aBuf, sizeof(aBuf),
"SELECT COUNT(*) AS NumFinished FROM %s_race WHERE Map='%s' AND Name='%s' ORDER BY time ASC LIMIT 1;",
pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr());
pSqlServer->executeSqlQuery(aBuf); pSqlServer->executeSqlQuery(aBuf);
if(!pSqlServer->GetResults()->next()) pSqlServer->GetResults()->first();
int NumFinished = pSqlServer->GetResults()->getInt("NumFinished");
if(NumFinished == 0)
{ {
str_format(aBuf, sizeof(aBuf), "SELECT Points FROM %s_maps WHERE Map ='%s'", pSqlServer->GetPrefix(), pData->m_Map.ClrStr()); str_format(aBuf, sizeof(aBuf), "SELECT Points FROM %s_maps WHERE Map ='%s'", pSqlServer->GetPrefix(), pData->m_Map.ClrStr());
pSqlServer->executeSqlQuery(aBuf); pSqlServer->executeSqlQuery(aBuf);
@ -543,37 +574,57 @@ bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlaye
if(pSqlServer->GetResults()->rowsCount() == 1) if(pSqlServer->GetResults()->rowsCount() == 1)
{ {
pSqlServer->GetResults()->next(); pSqlServer->GetResults()->next();
int points = pSqlServer->GetResults()->getInt("Points"); int Points = pSqlServer->GetResults()->getInt("Points");
if (points == 1) if(Points == 1)
str_format(aBuf, sizeof(aBuf), "You earned %d point for finishing this map!", points); str_format(paMessages[0], sizeof(paMessages[0]), "You earned %d point for finishing this map!", Points);
else else
str_format(aBuf, sizeof(aBuf), "You earned %d points for finishing this map!", points); str_format(paMessages[0], sizeof(paMessages[0]), "You earned %d points for finishing this map!", Points);
try str_format(aBuf, sizeof(aBuf),
{ "INSERT INTO %s_points(Name, Points) "
pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); "VALUES ('%s', '%d') "
} "ON duplicate key "
catch (CGameContextError &e) {} // just do nothing, it is not much of a problem if the player is not informed about points during mapchange "UPDATE Name=VALUES(Name), Points=Points+VALUES(Points);",
pSqlServer->GetPrefix(), pData->m_Name.ClrStr(), Points);
str_format(aBuf, sizeof(aBuf), "INSERT INTO %s_points(Name, Points) VALUES ('%s', '%d') ON duplicate key UPDATE Name=VALUES(Name), Points=Points+VALUES(Points);", pSqlServer->GetPrefix(), pData->m_Name.ClrStr(), points);
pSqlServer->executeSql(aBuf); pSqlServer->executeSql(aBuf);
} }
} }
// if no entry found... create a new one // save score
str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %s_race(Map, Name, Timestamp, Time, Server, cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, GameID, DDNet7) VALUES ('%s', '%s', '%s', '%.2f', '%s', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%s', false);", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), pData->m_aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName, pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], pData->m_aCpCurrent[24], pData->m_GameUuid.ClrStr()); str_format(aBuf, sizeof(aBuf),
"INSERT IGNORE INTO %s_race("
"Map, Name, Timestamp, Time, Server, "
"cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, "
"cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, "
"GameID, DDNet7) "
"VALUES ('%s', '%s', '%s', '%.2f', '%s', "
"'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', "
"'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', "
"'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', "
"'%s', false);",
pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr(),
pData->m_aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName,
pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2],
pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5],
pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8],
pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11],
pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14],
pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17],
pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20],
pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23],
pData->m_aCpCurrent[24], pData->m_GameUuid);
dbg_msg("sql", "%s", aBuf); dbg_msg("sql", "%s", aBuf);
pSqlServer->executeSql(aBuf); pSqlServer->executeSql(aBuf);
dbg_msg("sql", "Updating time done"); pData->m_pResult->m_Done = true;
dbg_msg("sql", "Saving score done");
return true; return true;
} }
catch (sql::SQLException &e) catch (sql::SQLException &e)
{ {
dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "MySQL Error: %s", e.what());
dbg_msg("sql", "ERROR: Could not update time"); dbg_msg("sql", "ERROR: Could not insert time");
} }
*/
return false; return false;
} }

View file

@ -19,11 +19,13 @@ struct CSqlPlayerResult
{ {
DIRECT, DIRECT,
ALL, ALL,
BROADCAST,
MAP_VOTE, // 3 Messages: Reason, Server, Map MAP_VOTE, // 3 Messages: Reason, Server, Map
PLAYER_INFO, PLAYER_INFO,
} m_MessageKind; } m_MessageKind;
char m_aaMessages[7][512]; char m_aaMessages[7][512];
union { union {
char m_Broadcast[1024];
struct { struct {
float m_Time; float m_Time;
float m_CpTime[NUM_CHECKPOINTS]; float m_CpTime[NUM_CHECKPOINTS];
@ -104,10 +106,11 @@ struct CSqlScoreData : CSqlData<CSqlPlayerResult>
{ {
using CSqlData<CSqlPlayerResult>::CSqlData; using CSqlData<CSqlPlayerResult>::CSqlData;
sqlstr::CSqlString<MAX_NAME_LENGTH> m_Map;
char m_GameUuid[UUID_MAXSTRSIZE];
sqlstr::CSqlString<MAX_NAME_LENGTH> m_Name; sqlstr::CSqlString<MAX_NAME_LENGTH> m_Name;
int m_ClientID; int m_ClientID;
bool m_NotEligible;
float m_Time; float m_Time;
char m_aTimestamp[TIMESTAMP_STR_LENGTH]; char m_aTimestamp[TIMESTAMP_STR_LENGTH];
float m_aCpCurrent[NUM_CHECKPOINTS]; float m_aCpCurrent[NUM_CHECKPOINTS];