1
0
Fork 0

Do not call into things we don't own in destructors

- Remove improper accesses in cChunk destructor
* Fixes #4894
This commit is contained in:
Tiger Wang 2020-09-22 21:21:47 +01:00
parent 10bd15a11e
commit 4519469547
9 changed files with 35 additions and 55 deletions

View File

@ -24,19 +24,6 @@ cJukeboxEntity::cJukeboxEntity(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, Ve
cJukeboxEntity::~cJukeboxEntity()
{
if (m_World && IsPlayingRecord())
{
// Stop playing music when destroyed by any means
m_World->BroadcastSoundParticleEffect(EffectID::SFX_RANDOM_PLAY_MUSIC_DISC, GetPos(), 0);
}
}
void cJukeboxEntity::Destroy(void) void cJukeboxEntity::Destroy(void)
{ {
ASSERT(m_World != nullptr); ASSERT(m_World != nullptr);

View File

@ -22,7 +22,6 @@ public: // tolua_export
BLOCKENTITY_PROTODEF(cJukeboxEntity) BLOCKENTITY_PROTODEF(cJukeboxEntity)
cJukeboxEntity(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, Vector3i a_Pos, cWorld * a_World); cJukeboxEntity(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, Vector3i a_Pos, cWorld * a_World);
virtual ~cJukeboxEntity() override;
// tolua_begin // tolua_begin

View File

@ -103,24 +103,9 @@ cChunk::cChunk(
cChunk::~cChunk() cChunk::~cChunk()
{ {
cPluginManager::Get()->CallHookChunkUnloaded(*m_World, m_PosX, m_PosZ);
// LOGINFO("### delete cChunk() (%i, %i) from %p, thread 0x%x ###", m_PosX, m_PosZ, this, GetCurrentThreadId()); // LOGINFO("### delete cChunk() (%i, %i) from %p, thread 0x%x ###", m_PosX, m_PosZ, this, GetCurrentThreadId());
m_BlockEntities.clear(); // Inform our neighbours that we're no longer valid:
// Remove and destroy all entities that are not players:
cEntityList Entities;
std::swap(Entities, m_Entities); // Need another list because cEntity destructors check if they've been removed from chunk
for (auto & Entity : Entities)
{
if (!Entity->IsPlayer())
{
// Scheduling a normal destruction is neither possible (Since this chunk will be gone till the schedule occurs) nor necessary.
Entity->DestroyNoScheduling(false); // No point in broadcasting in an unloading chunk. Chunks unload when no one is nearby.
}
}
if (m_NeighborXM != nullptr) if (m_NeighborXM != nullptr)
{ {
m_NeighborXM->m_NeighborXP = nullptr; m_NeighborXM->m_NeighborXP = nullptr;
@ -137,6 +122,7 @@ cChunk::~cChunk()
{ {
m_NeighborZP->m_NeighborZM = nullptr; m_NeighborZP->m_NeighborZM = nullptr;
} }
delete m_WaterSimulatorData; delete m_WaterSimulatorData;
m_WaterSimulatorData = nullptr; m_WaterSimulatorData = nullptr;
delete m_LavaSimulatorData; delete m_LavaSimulatorData;
@ -221,6 +207,25 @@ bool cChunk::CanUnloadAfterSaving(void) const
void cChunk::OnUnload()
{
// Note: this is only called during normal operation, not during shutdown
// Notify all entities of imminent unload:
for (auto & Entity : m_Entities)
{
// Chunks cannot be unloaded when they still contain players:
ASSERT(!Entity->IsPlayer());
// Notify the entity:
Entity->OnRemoveFromWorld(*Entity->GetWorld());
}
}
void cChunk::MarkSaving(void) void cChunk::MarkSaving(void)
{ {
m_IsSaving = true; m_IsSaving = true;

View File

@ -90,6 +90,11 @@ public:
/** Returns true if the chunk could have been unloaded if it weren't dirty */ /** Returns true if the chunk could have been unloaded if it weren't dirty */
bool CanUnloadAfterSaving(void) const; bool CanUnloadAfterSaving(void) const;
/** Called when the chunkmap unloads unused chunks.
Notifies contained entities that they are being unloaded and should for example, broadcast a destroy packet.
Not called during server shutdown; such cleanup during shutdown is unnecessary. */
void OnUnload();
bool IsLightValid(void) const {return m_IsLightValid; } bool IsLightValid(void) const {return m_IsLightValid; }
/* /*

View File

@ -1717,10 +1717,17 @@ void cChunkMap::UnloadUnusedChunks(void)
for (auto itr = m_Chunks.begin(); itr != m_Chunks.end();) for (auto itr = m_Chunks.begin(); itr != m_Chunks.end();)
{ {
if ( if (
(itr->second.CanUnload()) && // Can unload itr->second.CanUnload() && // Can unload
!cPluginManager::Get()->CallHookChunkUnloading(*GetWorld(), itr->first.ChunkX, itr->first.ChunkZ) // Plugins agree !cPluginManager::Get()->CallHookChunkUnloading(*GetWorld(), itr->first.ChunkX, itr->first.ChunkZ) // Plugins agree
) )
{ {
// First notify plugins:
cPluginManager::Get()->CallHookChunkUnloaded(*m_World, itr->first.ChunkX, itr->first.ChunkZ);
// Notify entities within the chunk, while everything's still valid:
itr->second.OnUnload();
// Kill the chunk:
itr = m_Chunks.erase(itr); itr = m_Chunks.erase(itr);
} }
else else

View File

@ -129,7 +129,6 @@ cClientHandle::~cClientHandle()
{ {
RemoveFromAllChunks(); RemoveFromAllChunks();
m_Player->GetWorld()->RemoveClientFromChunkSender(this); m_Player->GetWorld()->RemoveClientFromChunkSender(this);
m_Player->DestroyNoScheduling(true);
} }
// Send the Offline PlayerList packet: // Send the Offline PlayerList packet:
cRoot::Get()->BroadcastPlayerListsRemovePlayer(*m_Player); cRoot::Get()->BroadcastPlayerListsRemovePlayer(*m_Player);

View File

@ -250,21 +250,6 @@ void cEntity::Destroy()
void cEntity::DestroyNoScheduling(bool a_ShouldBroadcast)
{
SetIsTicking(false);
if (a_ShouldBroadcast)
{
m_World->BroadcastDestroyEntity(*this);
}
Destroyed();
}
void cEntity::TakeDamage(cEntity & a_Attacker) void cEntity::TakeDamage(cEntity & a_Attacker)
{ {
int RawDamage = a_Attacker.GetRawDamageAgainst(*this); int RawDamage = a_Attacker.GetRawDamageAgainst(*this);

View File

@ -341,9 +341,6 @@ public:
initialize clients with our position. */ initialize clients with our position. */
Vector3d GetLastSentPosition(void) const { return m_LastSentPosition; } Vector3d GetLastSentPosition(void) const { return m_LastSentPosition; }
/** Destroy the entity without scheduling memory freeing. This should only be used by cChunk or cClientHandle for internal memory management. */
void DestroyNoScheduling(bool a_ShouldBroadcast);
/** Makes this entity take damage specified in the a_TDI. /** Makes this entity take damage specified in the a_TDI.
The TDI is sent through plugins first, then applied. The TDI is sent through plugins first, then applied.
If it returns false, the entity hasn't receive any damage. */ If it returns false, the entity hasn't receive any damage. */

View File

@ -226,6 +226,7 @@ cWorld::cWorld(
cFile::CreateFolderRecursive(m_DataPath); cFile::CreateFolderRecursive(m_DataPath);
// TODO: unique ptr unnecessary
m_ChunkMap = std::make_unique<cChunkMap>(this); m_ChunkMap = std::make_unique<cChunkMap>(this);
m_ChunkMap->TrackInDeadlockDetect(a_DeadlockDetect, m_WorldName); m_ChunkMap->TrackInDeadlockDetect(a_DeadlockDetect, m_WorldName);
@ -964,11 +965,6 @@ void cWorld::Stop(cDeadlockDetect & a_DeadlockDetect)
m_MapManager.SaveMapData(); m_MapManager.SaveMapData();
} }
// Explicitly destroy the chunkmap, so that it's guaranteed to be destroyed before the other internals
// This fixes crashes on stopping the server, because chunk destructor deletes entities and those access the world.
// TODO: destructors should only be used for releasing resources, not doing extra work
m_ChunkMap.reset();
} }