Files
LGram16 dd97ddec92 Restructure repository to include all source folders
Move git root from Client/ to src/ to track all source code:
- Client: Game client source (moved to Client/Client/)
- Server: Game server source
- GameTools: Development tools
- CryptoSource: Encryption utilities
- database: Database scripts
- Script: Game scripts
- rylCoder_16.02.2008_src: Legacy coder tools
- GMFont, Game: Additional resources

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 20:17:20 +09:00

522 lines
18 KiB
C++

#ifndef _RYL_DBAGENT_DB_DATA_MGR_H_
#define _RYL_DBAGENT_DB_DATA_MGR_H_
#include <map>
#include <vector>
#include <utility>
#include <mmsystem.h>
#include <Log/ServerLogDefine.h>
#include <boost/pool/pool_alloc.hpp>
#include <boost/pool/object_pool.hpp>
#include "SessionData.h"
//forward decl.
class CDBComponent;
namespace DBAgent
{
namespace DataStorage
{
// forward decl.
class CSessionData;
enum DBDataError
{
DATA_SUCCEEDED = 0, //! 성공
DATA_SERVER_ERROR = 1, //! 메모리 할당 오류, 맵 삽입 오류 등
DATA_ALREADY_LOGIN_ERROR = 2, //! 이미 로그인 해 있음
DATA_ALREADY_LOGOUT_ERROR = 3, //! 이미 로그아웃 해 있음
MAX_DATA_ERROR = 4
};
extern const char* g_szDBDataErrorString[MAX_DATA_ERROR];
template<typename Key, typename Data, typename Pred = std::less<Key> >
class CDBDataMgr
{
public:
typedef typename Key KeyType;
typedef typename Data DataType;
enum Const { MAX_MANAGER_NAME = 32 };
// 첫번째 인자는 관리할 대상
// 두번째 인자는 로그인 맵은 마지막 업데이트 시간
// 로그아웃 맵은 삽입된 시간
typedef std::pair<DataType*, unsigned long> DBDataPair;
typedef std::map<KeyType, DBDataPair, Pred,
boost::fast_pool_allocator< std::pair<KeyType, DBDataPair> > > DataMap;
//! DB를 연결한다.
void SetDBComponent(CDBComponent* lpDBComponent) { m_lpDBComponent = lpDBComponent; }
CDBComponent* GetDBComponent() { return m_lpDBComponent; }
//! 데이터가 LoginDB에 있는지, LogoutDB에 있는지 알아 온다.
bool IsDataLoginDB(const KeyType& dataKey) { return m_LoginMap.end() != m_LoginMap.find(dataKey); }
bool IsDataLogoutDB(const KeyType& dataKey) { return m_LogoutMap.end() != m_LogoutMap.find(dataKey); }
//! 세팅된 관리자 이름을 얻어온다.
const char* GetManagerName() const { return m_szManagerName; }
unsigned int GetLoginNum() const { return static_cast<unsigned int>(m_LoginMap.size()); }
unsigned int GetLogoutNum() const { return static_cast<unsigned int>(m_LogoutMap.size()); }
//! 데이터를 얻어오거나, 얻어온 데이터를 반환한다.
virtual DBDataError LoadData(CSessionData& SessionData, KeyType dataKey, DataType*& lpLoadData_Out);
virtual DBDataError UnloadData(const CSessionData& SessionData, DataType* lpUnloadData);
DataType* GetLogoutData(const KeyType& dataKey); //! 로그아웃된 데이터를 얻어온다.
DBDataError ReloadData(const KeyType& dataKey); //! 데이터를 DB로부터 Reload한다.
bool RemoveLogoutData(const KeyType& dataKey); //! 로그아웃된 데이터를 캐쉬로부터 삭제한다.
//! 데이터 개수가 너무 많은 경우, 로그아웃한지 오래된 데이터를 하나씩 삭제한다.
bool ManageUnloadedData(unsigned long dwMaxDataNum);
//! 로그인 셋 / 로그아웃 셋에 있는 데이터를 업데이트한다.
//! Arg1 : 업데이트할 최소개수,
//! Arg2 : 업데이트에 소모할 최대 시간이다.
void UpdateDBData(unsigned long dwMinUpdateNum, unsigned long dwMaxUseTime);
//! 변경된 데이터를 전부 적용한 후 제거한다.
void Destroy()
{
Destroy(m_LoginMap.begin(), m_LoginMap.end());
Destroy(m_LogoutMap.begin(), m_LogoutMap.end());
m_LoginMap.clear();
m_LogoutMap.clear();
}
protected:
CDBDataMgr() : m_lpDBComponent(0) { memset(m_szManagerName, 0, sizeof(char) * MAX_MANAGER_NAME); }
~CDBDataMgr() { Destroy(); }
void SetManagerName(const char* szManagerName)
{
strncpy(m_szManagerName, szManagerName, MAX_MANAGER_NAME - 1);
m_szManagerName[MAX_MANAGER_NAME - 1] = 0;
}
// KeyType을 로그로 남길 방법을 알 수 없기 때문에 상속받아서 처리하도록 한다.
virtual void Log(LOG_TYPE eLogType,
const char* pRtn, const char* pFileName, int nLine,
const CSessionData* lpSessionData,
const KeyType& dataKey, const char* szErrorMsg) = 0;
// Data를 생성 및 삭제하는 메서드이다.
Data* CreateData() { return new Data; }
void DeleteData(Data* lpDBData) { delete lpDBData; }
/*
Data* CreateData() { return m_DataPool.construct(); }
void DeleteData(Data* lpDBData) { m_DataPool.destruct(lpDBData); }
*/
private:
void Destroy(typename DataMap::iterator first, typename DataMap::iterator last);
CDBComponent* m_lpDBComponent; //! DB와의 연결
DataMap m_LoginMap; //! 로그인 된 캐릭터 리스트
DataMap m_LogoutMap; //! 로그아웃된 캐릭터 리스트
boost::object_pool<Data> m_DataPool; //! 객체 풀
char m_szManagerName[MAX_MANAGER_NAME]; //! 관리 매니져의 이름.
};
template<typename Key, typename Data, typename Pred>
class CGreaterLogoutTime
{
public:
typedef typename CDBDataMgr<Key, Data, Pred>::DataMap::value_type DataType;
CGreaterLogoutTime(unsigned long dwCurrentTime) : m_dwCurrentTime(dwCurrentTime) { }
bool operator () (const DataType& lhs, const DataType& rhs)
{
return m_dwCurrentTime - lhs.second.second >
m_dwCurrentTime - rhs.second.second;
}
private:
unsigned long m_dwCurrentTime;
};
template<typename Key, typename Data, typename Pred>
class CGreaterUpdateTime
{
public:
typedef typename CDBDataMgr<Key, Data, Pred>::DataMap::value_type DataType;
CGreaterUpdateTime(unsigned long dwCurrentTime) : m_dwCurrentTime(dwCurrentTime) { }
bool operator () (const DataType& lhs, const DataType& rhs)
{
return m_dwCurrentTime - lhs.second.first->GetLastDBUpdateTime() >
m_dwCurrentTime - rhs.second.first->GetLastDBUpdateTime();
}
private:
unsigned long m_dwCurrentTime;
};
//! 데이터를 로드한 후 Session에 연결한다.
template<typename Key, typename Data, typename Pred>
DBDataError CDBDataMgr<Key, Data, Pred>::LoadData(CSessionData& SessionData, KeyType dataKey, Data*& lpLoadData_Out)
{
lpLoadData_Out = 0;
Data* lpDBData = 0;
DataMap::iterator pos;
DataMap::iterator end;
// 로그인 맵에 데이터가 있는지 살핀다. 있으면 로그인 실패.
pos = m_LoginMap.find(dataKey);
end = m_LoginMap.end();
if(pos != end)
{
Log(LOG_ERROR, LOG_FFL, &SessionData, dataKey, (pos->second.first->GetKey() != dataKey)
? "데이터 로드 실패 : 키 값이 다릅니다" : "데이터 로드 실패 : 이미 캐릭터가 로그인 해 있습니다");
return DATA_ALREADY_LOGIN_ERROR;
}
else
{
// 로그아웃 맵에서 검색합니다.
pos = m_LogoutMap.find(dataKey);
end = m_LogoutMap.end();
if(pos != end)
{
// 데이터가 있다. 로그아웃 맵에서 삭제하고, 세팅 준비를 한다.
lpDBData = pos->second.first;
m_LogoutMap.erase(pos);
}
else
{
// 데이터가 없다. 데이터 생성 및 로드를 한다.
if(0 != m_lpDBComponent)
{
lpDBData = CreateData();
if(0 != lpDBData)
{
if(!lpDBData->GetFromDB(*m_lpDBComponent, dataKey))
{
// DB에서 데이터 얻어오기 실패. GetFromDB내부에서 로그 찍음.
DeleteData(lpDBData);
lpDBData = 0;
}
}
else
{
Log(LOG_ERROR, LOG_FFL, &SessionData, dataKey, "데이터 로드 실패 : 메모리 할당에 실패했습니다");
}
}
else
{
Log(LOG_ERROR, LOG_FFL, &SessionData, dataKey, "데이터 로드 실패 : DB연결이 NULL입니다");
}
}
}
if(0 != lpDBData)
{
DBDataPair dataPair(lpDBData, timeGetTime());
if(!m_LoginMap.insert(std::make_pair(dataKey, dataPair)).second)
{
Log(LOG_ERROR, LOG_FFL, &SessionData, dataKey,
"데이터 로드 실패 : LoginMap에 데이터 삽입 실패(있을 수 없음)");
DeleteData(lpDBData);
lpDBData = 0;
}
else
{
Log(LOG_INFO, LOG_FFL, &SessionData, dataKey, "데이터 로드 성공");
lpLoadData_Out = lpDBData;
return DATA_SUCCEEDED;
}
}
return DATA_SERVER_ERROR;
}
//! 데이터를 Session에서 분리한 후 언로드한다.
template<typename Key, typename Data, typename Pred>
DBDataError CDBDataMgr<Key, Data, Pred>::UnloadData(const CSessionData& SessionData, Data* lpUnloadData)
{
if(0 != lpUnloadData)
{
KeyType dataKey = lpUnloadData->GetKey();
// 로그인 맵에서 찾아서 제거
if (0 == m_LoginMap.erase(dataKey))
{
Log(LOG_ERROR, LOG_FFL, &SessionData, dataKey, "데이터 언로드 이상 : 로그인 맵에 없는 데이터를 언로드합니다");
}
else if(0 == m_lpDBComponent || !lpUnloadData->UpdateDBData(*m_lpDBComponent))
{
// 데이터 업데이트 실패.
Log(LOG_ERROR, LOG_FFL, &SessionData, dataKey, "데이터 언로드 이상 : 업데이트에 실패하였습니다");
}
else if (m_LogoutMap.insert(
std::make_pair(dataKey, std::make_pair(lpUnloadData, timeGetTime()))).second)
{
// 데이터 업데이트 성공. 맵에 쑤셔 넣고 상호참조를 해제한다.
Log(LOG_INFO, LOG_FFL, &SessionData, dataKey, "데이터 언로드 성공");
// 성공. 여기서 탈출
return DATA_SUCCEEDED;
}
else
{
// 말도 안된다. 데이터가 복사될 우려가 있다.
Log(LOG_ERROR, LOG_FFL, &SessionData, dataKey, "데이터 언로드 실패 : Logout맵에 이미 데이터가 있습니다");
}
// 일단 덤프를 남기고 삭제하는 편이 좋다.
if(!lpUnloadData->WriteDataToDumpFile(0))
{
Log(LOG_ERROR, LOG_FFL, &SessionData, dataKey, "데이터 언로드 실패 : 언로드 실패 후, 데이터를 파일로 남기는 데 실패");
}
// 메모리 릭이 생겨도 어쩔 수 없다. 잘못 Delete하면 뻑난다;;
// DeleteData(lpUnloadData);
}
else
{
ERRLOG4(g_Log, "UID:%10u / CID:%10u / lpUnloadData:0x%p / %s 데이터 언로드 실패 : 인자가 이상합니다",
SessionData.GetUID(), SessionData.GetCID(), lpUnloadData, m_szManagerName);
}
return DATA_SERVER_ERROR;
}
//! 데이터를 DB로부터 Reload한다.
template<typename Key, typename Data, typename Pred>
DBDataError CDBDataMgr<Key, Data, Pred>::ReloadData(const KeyType& dataKey)
{
if(0 == m_lpDBComponent)
{
return DATA_SERVER_ERROR;
}
// 로그인 맵에 이미 있으면, 불러와서는 안된다.
DataMap::iterator pos = m_LoginMap.find(dataKey);
DataMap::iterator end = m_LoginMap.end();
if(pos != end)
{
return DATA_ALREADY_LOGIN_ERROR;
}
// 로그아웃 맵에 있으면, DB로부터 불러 오고, 없으면 그냥 리턴한다.
pos == m_LogoutMap.find(dataKey);
end == m_LogoutMap.end();
return (pos != end && !pos->second.first->GetFromDB(*m_lpDBComponent, dataKey))
? DATA_SERVER_ERROR : DATA_SUCCEEDED;
}
template<typename Key, typename Data, typename Pred>
bool CDBDataMgr<Key, Data, Pred>::RemoveLogoutData(const KeyType& dataKey)
{
// 로그아웃 캐쉬에 데이터가 있으면, 날려버린다.
DataMap::iterator pos = m_LogoutMap.find(dataKey);
DataMap::iterator end = m_LogoutMap.end();
if(pos != end)
{
DeleteData(pos->second.first);
m_LogoutMap.erase(pos);
return true;
}
return false;
}
//! 데이터 개수가 너무 많은 경우, 로그아웃한지 오래된 데이터를 하나씩 삭제한다.
template<typename Key, typename Data, typename Pred>
bool CDBDataMgr<Key, Data, Pred>::ManageUnloadedData(unsigned long dwMaxDataNum)
{
size_t nLogoutDataNum = m_LogoutMap.size();
if(dwMaxDataNum < nLogoutDataNum)
{
// 시간을 보고, 제일 오래된 캐릭을 내린다.
typedef std::vector<std::pair<KeyType, DBDataPair>,
boost::pool_allocator<std::pair<KeyType, DBDataPair> > > RemoveMap;
RemoveMap removeMap(nLogoutDataNum - dwMaxDataNum);
std::partial_sort_copy(m_LogoutMap.begin(), m_LogoutMap.end(),
removeMap.begin(), removeMap.end(), CGreaterLogoutTime<Key,Data,Pred>(timeGetTime()));
RemoveMap::iterator pos = removeMap.begin();
RemoveMap::iterator end = removeMap.end();
for(; pos != end; ++pos)
{
m_LogoutMap.erase(pos->first);
DeleteData(pos->second.first);
}
}
return true;
}
//! 로그인 셋에 있는 데이터를, 시간 순으로 정렬해서 가장 오래된 데이터부터
//! 업데이트한다. 인자는 업데이트에 소모할 최대 시간이다.
template<typename Key, typename Data, typename Pred>
void CDBDataMgr<Key, Data, Pred>::UpdateDBData(unsigned long dwMinUpdateNum, unsigned long dwMaxUseTime)
{
if(0 != m_lpDBComponent)
{
typedef std::vector<std::pair<KeyType, DBDataPair>,
boost::pool_allocator<std::pair<KeyType, DBDataPair> > > UpdateList;
UpdateList updateList(m_LoginMap.size());
unsigned long dwStartTime = timeGetTime();
std::partial_sort_copy(m_LoginMap.begin(), m_LoginMap.end(),
updateList.begin(), updateList.end(), CGreaterUpdateTime<Key,Data,Pred>(dwStartTime));
UpdateList::iterator loginPos = updateList.begin();
UpdateList::iterator loginEnd = updateList.end();
unsigned long dwCurrentTime = timeGetTime();
// 끝까지 돌았거나, 최소 개수 이상 업데이트하고, 시간이 오버되었으면 그만 한다.
for(unsigned long dwUpdateCount = 0; loginPos != loginEnd; ++loginPos)
{
if (dwMinUpdateNum < dwUpdateCount &&
dwCurrentTime < dwStartTime + dwMaxUseTime)
{
break;
}
Data* lpDBData = loginPos->second.first;
if(lpDBData->IsDataChanged())
{
lpDBData->UpdateDBData(*m_lpDBComponent);
++dwUpdateCount;
dwCurrentTime = timeGetTime();
}
}
// 캐쉬 DB쪽은. 대부분 업데이트 되어 있기 때문에,
// 최근 거부터 업데이트 해 줄 필요는 없다. 그냥 전부 한바퀴 돌아준다.
DataMap::iterator logoutPos = m_LogoutMap.begin();
DataMap::iterator logoutEnd = m_LogoutMap.end();
dwCurrentTime = timeGetTime();
for(unsigned long dwUpdateCount = 0; logoutPos != logoutEnd; ++logoutPos)
{
if (dwMinUpdateNum < dwUpdateCount &&
dwCurrentTime < dwStartTime + dwMaxUseTime)
{
break;
}
Data* lpDBData = logoutPos->second.first;
if(lpDBData->IsDataChanged())
{
lpDBData->UpdateDBData(*m_lpDBComponent);
++dwUpdateCount;
dwCurrentTime = timeGetTime();
}
}
}
}
template<typename Key, typename Data, typename Pred>
void CDBDataMgr<Key, Data, Pred>::Destroy(typename DataMap::iterator first, typename DataMap::iterator last)
{
for(; first != last; ++first)
{
Data* lpData = first->second.first;
if(0 == m_lpDBComponent || !lpData->UpdateDBData(*m_lpDBComponent))
{
// TODO : 최종 데이터 업데이트 실패. 캐릭터 기록을 file등으로 남긴다.
Log(LOG_ERROR, LOG_FFL, 0, lpData->GetKey(), lpData->WriteDataToDumpFile(0)
? "데이터 저장 실패 : 업데이트에 실패 후 파일로 남깁니다"
: "데이터 저장 실패 : 업데이트, 파일로 남기는 데 실패했습니다");
}
DeleteData(lpData);
}
}
template<typename Key, typename Data, typename Pred>
Data* CDBDataMgr<Key, Data, Pred>::GetLogoutData(const KeyType& dataKey)
{
DataMap::iterator logoutPos = m_LogoutMap.find(dataKey);
Data* lpDBData = 0;
if(logoutPos == m_LogoutMap.end())
{
// 로그아웃 맵에 데이터가 없는 경우
DataMap::iterator loginPos = m_LoginMap.find(dataKey);
if(loginPos == m_LoginMap.end())
{
// 로그인 맵에 들어있지 않은 경우만 데이터를 로드
lpDBData = CreateData();
if(0 != lpDBData)
{
if (lpDBData->GetFromDB(*m_lpDBComponent, dataKey) &&
m_LogoutMap.insert(
std::make_pair(dataKey, std::make_pair(lpDBData, timeGetTime()))).second)
{
Log(LOG_INFO, LOG_FFL, 0, dataKey, "로그아웃 데이터 로드 성공");
}
else
{
DeleteData(lpDBData);
lpDBData = 0;
}
}
}
}
else
{
// 로그아웃 맵에 데이터가 있는 경우
lpDBData = logoutPos->second.first;
}
return lpDBData;
}
}
}
#endif