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

View file

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

View file

@ -20,7 +20,7 @@
std::atomic_int CSqlScore::ms_InstanceCount(0);
CSqlPlayerResult::CSqlPlayerResult() :
m_Done(0)
m_Done(false)
{
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++)
m_aaMessages[i][0] = 0;
break;
case BROADCAST:
m_Data.m_Broadcast[0] = 0;
break;
case MAP_VOTE:
break;
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)
{
CConsole* pCon = (CConsole*)GameServer()->Console();
if(pCon->m_Cheated)
if(pCon->m_Cheated || NotEligible)
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_Name = Server()->ClientName(ClientID);
Tmp->m_Time = Time;
str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp));
Tmp->m_NotEligible = NotEligible;
for(int i = 0; i < NUM_CHECKPOINTS; i++)
Tmp->m_aCpCurrent[i] = CpTime[i];
@ -494,48 +503,70 @@ void CSqlScore::SaveScore(int ClientID, float Time, const char *pTimestamp, floa
bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure)
{
/*
const CSqlScoreData *pData = dynamic_cast<const CSqlScoreData *>(pGameData);
auto paMessages = pData->m_pResult->m_aaMessages;
if (HandleFailure)
if(HandleFailure)
{
if (!g_Config.m_SvSqlFailureFile[0])
if(!g_Config.m_SvSqlFailureFile[0])
return true;
lock_wait(ms_FailureFileLock);
IOHANDLE File = io_open(g_Config.m_SvSqlFailureFile, IOFLAG_APPEND);
if(File)
if(File == 0)
{
dbg_msg("sql", "ERROR: Could not save Score, writing insert to a file now...");
char aBuf[768];
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());
io_write(File, aBuf, str_length(aBuf));
io_write_newline(File);
io_close(File);
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);
return true;
lock_unlock(ms_FailureFileLock);
dbg_msg("sql", "ERROR: Could not save Score, NOT even to a file");
return false;
}
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...");
if(pData->m_NotEligible)
{
return false;
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);
io_write(File, aBuf, str_length(aBuf));
io_write_newline(File);
io_close(File);
lock_unlock(ms_FailureFileLock);
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;
}
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);
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());
pSqlServer->executeSqlQuery(aBuf);
@ -543,37 +574,57 @@ bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlaye
if(pSqlServer->GetResults()->rowsCount() == 1)
{
pSqlServer->GetResults()->next();
int points = pSqlServer->GetResults()->getInt("Points");
if (points == 1)
str_format(aBuf, sizeof(aBuf), "You earned %d point for finishing this map!", points);
int Points = pSqlServer->GetResults()->getInt("Points");
if(Points == 1)
str_format(paMessages[0], sizeof(paMessages[0]), "You earned %d point for finishing this map!", Points);
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
{
pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
}
catch (CGameContextError &e) {} // just do nothing, it is not much of a problem if the player is not informed about points during mapchange
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);
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);
}
}
// if no entry found... create a new one
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());
// 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);
dbg_msg("sql", "%s", aBuf);
pSqlServer->executeSql(aBuf);
dbg_msg("sql", "Updating time done");
pData->m_pResult->m_Done = true;
dbg_msg("sql", "Saving score done");
return true;
}
catch (sql::SQLException &e)
{
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;
}

View file

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