summaryrefslogtreecommitdiffstats
path: root/src/ClientHandle.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ClientHandle.cpp')
-rw-r--r--src/ClientHandle.cpp72
1 files changed, 49 insertions, 23 deletions
diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp
index 8ae81f645..f4aa3e0c5 100644
--- a/src/ClientHandle.cpp
+++ b/src/ClientHandle.cpp
@@ -104,24 +104,7 @@ cClientHandle::~cClientHandle()
{
ASSERT(m_State == eState::csDestroyed); // Has Destroy() been called?
- if (m_Player != nullptr)
- {
- cWorld * World = m_Player->GetWorld();
-
- // Upon clienthandle destruction, the world pointer of a valid player SHALL NOT be null.
- // The only time where it can be null is after player construction but before cEntity::Initialize is called in the queued task in Authenticate.
- // During this time, it is guaranteed that the clienthandle is not deleted.
- ASSERT(World != nullptr);
-
- World->QueueTask(
- [this](cWorld & a_World)
- {
- a_World.RemovePlayer(m_Player);
- a_World.BroadcastPlayerListRemovePlayer(*m_Player);
- m_Player->Destroy();
- }
- );
- }
+ // Note: don't handle player destruction here because we don't own them, so problems arise during shutdown
LOGD("Deletied client \"%s\" at %p", GetUsername().c_str(), static_cast<void *>(this));
}
@@ -144,6 +127,38 @@ void cClientHandle::Destroy(void)
m_Link.reset();
}
+ if (m_Player != nullptr)
+ {
+ cWorld * World = m_Player->GetWorld();
+
+ // Upon Destroy, the world pointer of a valid player SHALL NOT be null.
+ // Guaranteed by Destroy and Authenticate being called in the same thread
+ ASSERT(World != nullptr);
+
+ // We do not leak a player object as Authenticate checks to see if Destroy was called before creating a player
+ World->QueueTask(
+ [this](cWorld & a_World)
+ {
+ if (m_Player->GetParentChunk() == nullptr)
+ {
+ // Player not yet initialised; calling Destroy is not valid
+ LOGWARN("ParentChunk null");
+ return;
+ }
+
+ if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*m_Player))
+ {
+ cRoot::Get()->BroadcastChatLeave(Printf("%s has left the game", m_Player->GetName().c_str()));
+ LOGINFO("Player %s has left the game", m_Player->GetName().c_str());
+ }
+
+ a_World.RemovePlayer(m_Player);
+ a_World.BroadcastPlayerListRemovePlayer(*m_Player);
+ m_Player->Destroy();
+ }
+ );
+ }
+
RemoveFromWorld();
LOGD("%s: client %p, \"%s\"", __FUNCTION__, static_cast<void *>(this), m_Username.c_str());
@@ -309,8 +324,11 @@ void cClientHandle::Kick(const AString & a_Reason)
void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID, const Json::Value & a_Properties)
{
+ ASSERT(cRoot::Get()->GetServer()->IsInTickThread());
+
if (m_State != eState::csAuthenticating)
{
+ // TODO: is this necessary?
return;
}
@@ -348,12 +366,22 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID,
}
m_Player->SetIP(m_IPString);
+ // So Destroy always has a valid world for a valid player
+ // Additionally, plugins expect world to be set
+ m_Player->SetWorld(World);
+
cpp14::move_on_copy_wrapper<decltype(Player)> PlayerPtr(std::move(Player));
World->QueueTask(
[World, Player = m_Player, PlayerPtr, Client](cWorld & a_World) mutable
{
- // Plugins expect world to be set:
- Player->SetWorld(World);
+ // We're in the task to create the player - any other QueueTask will execute in the next tick
+ // So, check to see cClientHandle::Destroy has not been called (State >= IsDestroying where State is monotonic)
+ if (Client->IsDestroying() || Client->IsDestroyed())
+ {
+ // Destroy called - proceeding now means we may create a player object that's never deleted
+ LOGWARN("Destroyed");
+ return;
+ }
if (!cRoot::Get()->GetPluginManager()->CallHookPlayerJoined(*Player))
{
@@ -386,9 +414,7 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID,
cRoot::Get()->BroadcastPlayerListsAddPlayer(*Player);
cRoot::Get()->SendPlayerLists(Player);
- // Note: cEnttiy::Initialize takes ownership of the player object, however:
- // 1. It is guaranteed to exist whilst the server is running and this clienthandle is alive
- // 2. In the case that the server is stopping, it is guaranteed that we are destroyed before the player object is destroyed
+ // Note: cEnttiy::Initialize takes ownership of the player object
Player->Initialize(std::move(PlayerPtr.value), *World);
World->AddPlayer(Player);