core: add clientless player lifecycle state machine
This commit is contained in:
1 parent
7013087bcd
commit
0c2e333bb4
4 files changed
+97
-9
No files matched your search
@@ -690,6 +690,10 @@ class TC_GAME_API WorldSession
|
||||
// Initializes shared account/session state and then starts the normal
|
||||
// Player DB-load/login pipeline for a registered Clientless session.
|
||||
bool InitializeClientlessPlayer(ObjectGuid playerGuid);
|
||||
bool HasPendingClientlessPlayerLogin() const
|
||||
{
|
||||
return IsClientless() && (!_clientlessPendingPlayerGuid.IsEmpty() || PlayerLoading());
|
||||
}
|
||||
|
||||
// Starts the normal Player DB-load/login pipeline without relying on
|
||||
// client character-enumeration state or a network login opcode.
|
||||
|
||||
@@ -185,6 +185,7 @@ World::~World()
|
||||
delete m_clientlessSessions.begin()->second;
|
||||
m_clientlessSessions.erase(m_clientlessSessions.begin());
|
||||
}
|
||||
m_clientlessPlayerStates.clear();
|
||||
|
||||
CliCommandHolder* command = nullptr;
|
||||
while (cliCmdQueue.next(command))
|
||||
@@ -323,6 +324,15 @@ WorldSession* World::FindClientlessSession(uint32 characterGuid) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ClientlessPlayerState World::GetClientlessPlayerState(uint32 characterGuid) const
|
||||
{
|
||||
auto itr = m_clientlessPlayerStates.find(characterGuid);
|
||||
if (itr == m_clientlessPlayerStates.end())
|
||||
return ClientlessPlayerState::Offline;
|
||||
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
bool World::AddClientlessSession(uint32 characterGuid, WorldSession* session)
|
||||
{
|
||||
if (!characterGuid || !session)
|
||||
@@ -360,7 +370,9 @@ bool World::AddClientlessSession(uint32 characterGuid, WorldSession* session)
|
||||
}
|
||||
|
||||
m_clientlessSessions[characterGuid] = session;
|
||||
TC_LOG_INFO("server.worldserver", "Registered clientless session for character GUID %u (account %u).",
|
||||
m_clientlessPlayerStates[characterGuid] = ClientlessPlayerState::Loading;
|
||||
TC_LOG_INFO("server.worldserver",
|
||||
"Registered clientless session for character GUID %u (account %u), state=Loading.",
|
||||
characterGuid, session->GetAccountId());
|
||||
return true;
|
||||
}
|
||||
@@ -371,11 +383,14 @@ bool World::RemoveClientlessSession(uint32 characterGuid)
|
||||
if (itr == m_clientlessSessions.end())
|
||||
return false;
|
||||
|
||||
WorldSession* session = itr->second;
|
||||
m_clientlessSessions.erase(itr);
|
||||
ClientlessPlayerState& state = m_clientlessPlayerStates[characterGuid];
|
||||
if (state == ClientlessPlayerState::Removing)
|
||||
return true;
|
||||
|
||||
TC_LOG_INFO("server.worldserver", "Removing clientless session for character GUID %u.", characterGuid);
|
||||
delete session;
|
||||
state = ClientlessPlayerState::Removing;
|
||||
TC_LOG_INFO("server.worldserver",
|
||||
"Clientless removal requested for character GUID %u (account %u), state=Removing.",
|
||||
characterGuid, itr->second ? itr->second->GetAccountId() : 0u);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -389,10 +404,10 @@ bool World::CreateClientlessPlayerSession(uint32 accountId, uint32 characterGuid
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FindClientlessSession(characterGuid))
|
||||
if (GetClientlessPlayerState(characterGuid) != ClientlessPlayerState::Offline || FindClientlessSession(characterGuid))
|
||||
{
|
||||
TC_LOG_ERROR("server.worldserver",
|
||||
"World::CreateClientlessPlayerSession rejected duplicate character GUID %u.",
|
||||
"World::CreateClientlessPlayerSession rejected character GUID %u because its lifecycle is not Offline.",
|
||||
characterGuid);
|
||||
return false;
|
||||
}
|
||||
@@ -405,6 +420,17 @@ bool World::CreateClientlessPlayerSession(uint32 accountId, uint32 characterGuid
|
||||
return false;
|
||||
}
|
||||
|
||||
for (SessionMap::const_iterator itr = m_clientlessSessions.begin(); itr != m_clientlessSessions.end(); ++itr)
|
||||
{
|
||||
if (itr->second && itr->second->GetAccountId() == accountId)
|
||||
{
|
||||
TC_LOG_ERROR("server.worldserver",
|
||||
"World::CreateClientlessPlayerSession rejected account %u because character GUID %u already owns a clientless session.",
|
||||
accountId, itr->first);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QueryResult characterResult = CharacterDatabase.PQuery(
|
||||
"SELECT account FROM characters WHERE guid = %u", characterGuid);
|
||||
if (!characterResult)
|
||||
@@ -472,7 +498,9 @@ bool World::CreateClientlessPlayerSession(uint32 accountId, uint32 characterGuid
|
||||
ObjectGuid playerGuid = ObjectGuid::Create<HighGuid::Player>(characterGuid);
|
||||
if (!session->InitializeClientlessPlayer(playerGuid))
|
||||
{
|
||||
RemoveClientlessSession(characterGuid);
|
||||
m_clientlessSessions.erase(characterGuid);
|
||||
m_clientlessPlayerStates.erase(characterGuid);
|
||||
delete session;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3361,14 +3389,18 @@ void World::UpdateSessions(uint32 diff)
|
||||
|
||||
// Clientless sessions share normal WorldSession callbacks/update machinery,
|
||||
// but are intentionally outside the network session registry and queue.
|
||||
// Explicit states keep async initialization/player loading from being
|
||||
// destroyed underneath an in-flight callback.
|
||||
for (SessionMap::iterator itr = m_clientlessSessions.begin(), next; itr != m_clientlessSessions.end(); itr = next)
|
||||
{
|
||||
next = itr;
|
||||
++next;
|
||||
|
||||
uint32 characterGuid = itr->first;
|
||||
WorldSession* session = itr->second;
|
||||
if (!session)
|
||||
{
|
||||
m_clientlessPlayerStates.erase(characterGuid);
|
||||
m_clientlessSessions.erase(itr);
|
||||
continue;
|
||||
}
|
||||
@@ -3376,6 +3408,48 @@ void World::UpdateSessions(uint32 diff)
|
||||
WorldSessionFilter updater(session);
|
||||
if (!session->Update(diff, updater))
|
||||
{
|
||||
m_clientlessPlayerStates.erase(characterGuid);
|
||||
delete session;
|
||||
m_clientlessSessions.erase(itr);
|
||||
continue;
|
||||
}
|
||||
|
||||
ClientlessPlayerState& state = m_clientlessPlayerStates[characterGuid];
|
||||
|
||||
if (state == ClientlessPlayerState::Loading)
|
||||
{
|
||||
if (Player* player = session->GetPlayer())
|
||||
{
|
||||
if (player->IsInWorld())
|
||||
{
|
||||
state = ClientlessPlayerState::Online;
|
||||
TC_LOG_INFO("server.worldserver",
|
||||
"Clientless player character GUID %u (account %u) reached state=Online.",
|
||||
characterGuid, session->GetAccountId());
|
||||
}
|
||||
}
|
||||
else if (!session->HasPendingClientlessPlayerLogin())
|
||||
{
|
||||
TC_LOG_ERROR("server.worldserver",
|
||||
"Clientless player character GUID %u (account %u) left Loading without a Player; cleaning up.",
|
||||
characterGuid, session->GetAccountId());
|
||||
m_clientlessPlayerStates.erase(characterGuid);
|
||||
delete session;
|
||||
m_clientlessSessions.erase(itr);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (state == ClientlessPlayerState::Removing)
|
||||
{
|
||||
if (session->HasPendingClientlessPlayerLogin())
|
||||
continue;
|
||||
|
||||
TC_LOG_INFO("server.worldserver",
|
||||
"Clientless player character GUID %u (account %u) reached state=Offline.",
|
||||
characterGuid, session->GetAccountId());
|
||||
|
||||
m_clientlessPlayerStates.erase(characterGuid);
|
||||
delete session;
|
||||
m_clientlessSessions.erase(itr);
|
||||
}
|
||||
|
||||
@@ -596,6 +596,14 @@ struct CharacterInfo
|
||||
uint32 ArenaTeamId[3];
|
||||
};
|
||||
|
||||
enum class ClientlessPlayerState : uint8
|
||||
{
|
||||
Offline,
|
||||
Loading,
|
||||
Online,
|
||||
Removing
|
||||
};
|
||||
|
||||
/// The World
|
||||
class TC_GAME_API World
|
||||
{
|
||||
@@ -613,6 +621,7 @@ class TC_GAME_API World
|
||||
bool AddClientlessSession(uint32 characterGuid, WorldSession* session);
|
||||
bool RemoveClientlessSession(uint32 characterGuid);
|
||||
bool CreateClientlessPlayerSession(uint32 accountId, uint32 characterGuid);
|
||||
ClientlessPlayerState GetClientlessPlayerState(uint32 characterGuid) const;
|
||||
SessionMap const& GetAllClientlessSessions() const { return m_clientlessSessions; }
|
||||
uint32 GetClientlessSessionCount() const { return uint32(m_clientlessSessions.size()); }
|
||||
void AddInstanceSocket(std::weak_ptr<WorldSocket> sock, uint64 connectToKey);
|
||||
@@ -866,6 +875,7 @@ class TC_GAME_API World
|
||||
|
||||
SessionMap m_sessions; // Network sessions, keyed by account id
|
||||
SessionMap m_clientlessSessions; // Clientless sessions, keyed by character low GUID
|
||||
std::unordered_map<uint32, ClientlessPlayerState> m_clientlessPlayerStates;
|
||||
typedef std::unordered_map<uint32, time_t> DisconnectMap;
|
||||
DisconnectMap m_disconnects;
|
||||
uint32 m_maxActiveSessionCount;
|
||||
|
||||
@@ -672,7 +672,7 @@ namespace VianoBotCommands
|
||||
}
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"Clientless session removed for character GUID %u (account %u).",
|
||||
"Clientless logout requested for character GUID %u (account %u).",
|
||||
characterGuid, accountId);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user