From 7d4934534e9c58a111215859ba83c32a9bc0fa8a Mon Sep 17 00:00:00 2001 From: Mat Date: Thu, 5 Mar 2020 12:52:34 +0200 Subject: Stabilise MoveToWorld (#4004) * Stabilise MoveToWorld * Fix comments and deprecate ScheduleMoveToWorld * Enhanced thread safety for m_WorldChangeInfo * Return unique_ptr from cAtomicUniquePtr::exchange * cWorld now calls entity cEntity::OnAddToWorld and cEntity::OnRemoveFromWorld. Allows broadcasting entities added to the world from the world's tick thread. This also factors out some common code from cEntity::DoMoveToWorld and cEntity::Initialize. As a consequence, cEntity::Destroy(false) (i.e. Destroying the entity without broadcasting) is impossible. This isn't used anywhere in Cuberite so it's now deprecated. * Update entity position after removing it from the world. Fixes broadcasts being sent to the wrong chunk. * Fix style * cEntity: Update LastSentPosition when sending spawn packet * Add Wno-deprecated-declarations to the lua bindings * Kill uses of ScheduleMoveToWorld --- src/Bindings/CMakeLists.txt | 3 +- src/ClientHandle.cpp | 2 +- src/Entities/Boat.cpp | 2 +- src/Entities/Entity.cpp | 193 ++++++++++++++++++++----------------- src/Entities/Entity.h | 73 +++++++++++--- src/Entities/ExpOrb.cpp | 6 +- src/Entities/FallingBlock.cpp | 8 +- src/Entities/Floater.cpp | 2 +- src/Entities/Pickup.cpp | 8 +- src/Entities/Player.cpp | 120 ++++++++++------------- src/Entities/Player.h | 6 +- src/Entities/TNTEntity.cpp | 2 +- src/Entities/WitherSkullEntity.cpp | 2 +- src/Items/ItemFishingRod.h | 2 +- src/Mobs/Monster.cpp | 22 ++++- src/Mobs/Monster.h | 4 +- src/NetherPortalScanner.cpp | 2 +- src/OSSupport/AtomicUniquePtr.h | 81 ++++++++++++++++ src/OSSupport/CMakeLists.txt | 1 + src/World.cpp | 36 ++++--- src/World.h | 3 +- 21 files changed, 366 insertions(+), 212 deletions(-) create mode 100644 src/OSSupport/AtomicUniquePtr.h (limited to 'src') diff --git a/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt index b1a19beca..17f2bdf39 100644 --- a/src/Bindings/CMakeLists.txt +++ b/src/Bindings/CMakeLists.txt @@ -164,7 +164,8 @@ set_source_files_properties(${BINDING_OUTPUTS} PROPERTIES GENERATED TRUE) set_source_files_properties(${CMAKE_SOURCE_DIR}/src/Bindings/Bindings.cpp PROPERTIES COMPILE_FLAGS -Wno-error) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - set_source_files_properties(Bindings.cpp PROPERTIES COMPILE_FLAGS "-Wno-old-style-cast -Wno-missing-prototypes") + set_source_files_properties(Bindings.cpp PROPERTIES COMPILE_FLAGS + "-Wno-old-style-cast -Wno-missing-prototypes -Wno-deprecated-declarations") endif() if(NOT MSVC) diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 49a882c77..ce6d4c981 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -189,7 +189,7 @@ void cClientHandle::Destroy(void) // If ownership was transferred, our own smart pointer should be unset ASSERT(!m_PlayerPtr); - m_PlayerPtr = world->RemovePlayer(*player, true); + m_PlayerPtr = world->RemovePlayer(*player); // And RemovePlayer should have returned a valid smart pointer ASSERT(m_PlayerPtr); diff --git a/src/Entities/Boat.cpp b/src/Entities/Boat.cpp index c05e67275..c88df4952 100644 --- a/src/Entities/Boat.cpp +++ b/src/Entities/Boat.cpp @@ -60,7 +60,7 @@ bool cBoat::DoTakeDamage(TakeDamageInfo & TDI) m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 0, 0, 0, true); } } - Destroy(true); + Destroy(); } return true; } diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 57a4680bd..44808e2a5 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -47,7 +47,6 @@ cEntity::cEntity(eEntityType a_EntityType, Vector3d a_Pos, double a_Width, doubl m_LastPosition(a_Pos), m_EntityType(a_EntityType), m_World(nullptr), - m_IsWorldChangeScheduled(false), m_IsFireproof(false), m_TicksSinceLastBurnDamage(0), m_TicksSinceLastLavaDamage(0), @@ -158,19 +157,29 @@ bool cEntity::Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld) cPluginManager::Get()->CallHookSpawnedEntity(a_EntityWorld, *this); + return true; +} + + + + + +void cEntity::OnAddToWorld(cWorld & a_World) +{ // Spawn the entity on the clients: - a_EntityWorld.BroadcastSpawnEntity(*this); + m_LastSentPosition = GetPosition(); + a_World.BroadcastSpawnEntity(*this); + BroadcastLeashedMobs(); +} - // If has any mob leashed broadcast every leashed entity to this - if (HasAnyMobLeashed()) - { - for (auto LeashedMob : m_LeashedMobs) - { - m_World->BroadcastLeashEntity(*LeashedMob, *this); - } - } - return true; + + + +void cEntity::OnRemoveFromWorld(cWorld & a_World) +{ + RemoveAllLeashedMobs(); + a_World.BroadcastDestroyEntity(*this); } @@ -216,7 +225,7 @@ void cEntity::SetParentChunk(cChunk * a_Chunk) -void cEntity::Destroy(bool a_ShouldBroadcast) +void cEntity::Destroy() { SetIsTicking(false); @@ -226,11 +235,6 @@ void cEntity::Destroy(bool a_ShouldBroadcast) m_LeashedMobs.front()->Unleash(true, true); } - if (a_ShouldBroadcast) - { - m_World->BroadcastDestroyEntity(*this); - } - auto ParentChunkCoords = cChunkDef::BlockToChunk(GetPosition()); m_World->QueueTask([this, ParentChunkCoords](cWorld & a_World) { @@ -1166,7 +1170,7 @@ void cEntity::ApplyFriction(Vector3d & a_Speed, double a_SlowdownMultiplier, flo void cEntity::TickBurning(cChunk & a_Chunk) { // If we're about to change worlds, then we can't accurately determine whether we're in lava (#3939) - if (m_IsWorldChangeScheduled) + if (IsWorldChangeScheduled()) { return; } @@ -1310,34 +1314,11 @@ void cEntity::DetectCacti(void) -void cEntity::ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_SetPortalCooldown, bool a_ShouldSendRespawn) -{ - m_NewWorld = a_World; - m_NewWorldPosition = a_NewPosition; - m_IsWorldChangeScheduled = true; - m_WorldChangeSetPortalCooldown = a_SetPortalCooldown; - m_WorldChangeSendRespawn = a_ShouldSendRespawn; -} - - - - - bool cEntity::DetectPortal() { - // If somebody scheduled a world change with ScheduleMoveToWorld, change worlds now. - if (m_IsWorldChangeScheduled) + // If somebody scheduled a world change, do nothing. + if (IsWorldChangeScheduled()) { - m_IsWorldChangeScheduled = false; - - if (m_WorldChangeSetPortalCooldown) - { - // Delay the portal check. - m_PortalCooldownData.m_TicksDelayed = 0; - m_PortalCooldownData.m_ShouldPreventTeleportation = true; - } - - MoveToWorld(m_NewWorld, m_WorldChangeSendRespawn, m_NewWorldPosition); return true; } @@ -1519,69 +1500,81 @@ bool cEntity::DetectPortal() -bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) +void cEntity::DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo) { - UNUSED(a_ShouldSendRespawn); - ASSERT(a_World != nullptr); + ASSERT(a_WorldChangeInfo.m_NewWorld != nullptr); - if (GetWorld() == a_World) + if (a_WorldChangeInfo.m_SetPortalCooldown) { - // Don't move to same world - return false; + m_PortalCooldownData.m_TicksDelayed = 0; + m_PortalCooldownData.m_ShouldPreventTeleportation = true; } - // Ask the plugins if the entity is allowed to changing the world - if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World)) + if (GetWorld() == a_WorldChangeInfo.m_NewWorld) { - // A Plugin doesn't allow the entity to changing the world - return false; + // Moving to same world, don't need to remove from world + SetPosition(a_WorldChangeInfo.m_NewPosition); + return; } + LOGD("Warping entity #%i (%s) from world \"%s\" to \"%s\". Source chunk: (%d, %d) ", + GetUniqueID(), GetClass(), + m_World->GetName(), a_WorldChangeInfo.m_NewWorld->GetName(), + GetChunkX(), GetChunkZ() + ); + // Stop ticking, in preperation for detaching from this world. SetIsTicking(false); - // Tell others we are gone - GetWorld()->BroadcastDestroyEntity(*this); + // Remove from the old world + auto Self = m_World->RemoveEntity(*this); - // Take note of old chunk coords - auto OldChunkCoords = cChunkDef::BlockToChunk(GetPosition()); + // Update entity before calling hook + ResetPosition(a_WorldChangeInfo.m_NewPosition); + SetWorld(a_WorldChangeInfo.m_NewWorld); - // Set position to the new position - ResetPosition(a_NewPosition); + cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*this, *m_World); - // Stop all mobs from targeting this entity - // Stop this entity from targeting other mobs - if (this->IsMob()) - { - cMonster * Monster = static_cast(this); - Monster->SetTarget(nullptr); - Monster->StopEveryoneFromTargetingMe(); - } - - // Queue add to new world and removal from the old one - cWorld * OldWorld = GetWorld(); - SetWorld(a_World); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value - OldWorld->QueueTask([this, OldChunkCoords, a_World](cWorld & a_OldWorld) - { - LOGD("Warping entity #%i (%s) from world \"%s\" to \"%s\". Source chunk: (%d, %d) ", - this->GetUniqueID(), this->GetClass(), - a_OldWorld.GetName().c_str(), a_World->GetName().c_str(), - OldChunkCoords.m_ChunkX, OldChunkCoords.m_ChunkZ - ); - UNUSED(OldChunkCoords); // Non Debug mode only - a_World->AddEntity(a_OldWorld.RemoveEntity(*this)); - cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*this, a_OldWorld); - }); - return true; + // Don't do anything after adding as the old world's CS no longer protects us + a_WorldChangeInfo.m_NewWorld->AddEntity(std::move(Self)); } -bool cEntity::MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) +bool cEntity::MoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_SetPortalCooldown, bool a_ShouldSendRespawn) { - return DoMoveToWorld(a_World, a_ShouldSendRespawn, a_NewPosition); + ASSERT(a_World != nullptr); + + // Ask the plugins if the entity is allowed to change world + if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World)) + { + // A Plugin isn't allowing the entity to change world + return false; + } + + // Create new world change info + auto NewWCI = cpp14::make_unique(); + *NewWCI = { a_World, a_NewPosition, a_SetPortalCooldown, a_ShouldSendRespawn }; + + // Publish atomically + auto OldWCI = m_WorldChangeInfo.exchange(std::move(NewWCI)); + + if (OldWCI == nullptr) + { + // Schedule a new world change. + GetWorld()->QueueTask( + [this](cWorld & a_CurWorld) + { + auto WCI = m_WorldChangeInfo.exchange(nullptr); + cWorld::cLock Lock(a_CurWorld); + DoMoveToWorld(*WCI); + } + ); + } + + return true; } @@ -1606,7 +1599,7 @@ bool cEntity::MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn) return false; } - return DoMoveToWorld(World, a_ShouldSendRespawn, Vector3d(World->GetSpawnX(), World->GetSpawnY(), World->GetSpawnZ())); + return MoveToWorld(World, Vector3d(World->GetSpawnX(), World->GetSpawnY(), World->GetSpawnZ()), false, a_ShouldSendRespawn); } @@ -2253,6 +2246,34 @@ void cEntity::RemoveLeashedMob(cMonster * a_Monster) +void cEntity::RemoveAllLeashedMobs() +{ + while (!m_LeashedMobs.empty()) + { + m_LeashedMobs.front()->Unleash(false, true); + } +} + + + + + +void cEntity::BroadcastLeashedMobs() +{ + // If has any mob leashed broadcast every leashed entity to this + if (HasAnyMobLeashed()) + { + for (auto LeashedMob : m_LeashedMobs) + { + m_World->BroadcastLeashEntity(*LeashedMob, *this); + } + } +} + + + + + float cEntity::GetExplosionExposureRate(Vector3d a_ExplosionPosition, float a_ExlosionPower) { double EntitySize = m_Width * m_Width * m_Height; diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 2805ee9e0..b151f745d 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -2,6 +2,7 @@ #pragma once #include "../Item.h" +#include "../OSSupport/AtomicUniquePtr.h" @@ -72,6 +73,16 @@ struct TakeDamageInfo // tolua_begin class cEntity { +protected: + /** State variables for MoveToWorld. */ + struct sWorldChangeInfo + { + cWorld * m_NewWorld; + Vector3d m_NewPosition; + bool m_SetPortalCooldown; + bool m_SendRespawn; + }; + public: enum eEntityType @@ -163,6 +174,16 @@ public: Adds the entity to the world. */ virtual bool Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld); + /** Called when the entity is added to a world. + e.g after first spawning or after successfuly moving between worlds. + \param a_World The world being added to. */ + virtual void OnAddToWorld(cWorld & a_World); + + /** Called when the entity is removed from a world. + e.g. When the entity is destroyed or moved to a different world. + \param a_World The world being removed from. */ + virtual void OnRemoveFromWorld(cWorld & a_World); + // tolua_begin eEntityType GetEntityType(void) const { return m_EntityType; } @@ -268,8 +289,14 @@ public: If this returns false, you must stop using the cEntity pointer you have. */ bool IsTicking(void) const; - /** Destroys the entity and schedules it for memory freeing; if a_ShouldBroadcast is set to true, broadcasts the DestroyEntity packet */ - virtual void Destroy(bool a_ShouldBroadcast = true); + /** Destroys the entity, schedules it for memory freeing and broadcasts the DestroyEntity packet */ + virtual void Destroy(); + + OBSOLETE void Destroy(bool a_ShouldBroadcast) + { + LOGWARNING("cEntity:Destory(bool) is deprecated, use cEntity:Destroy() instead."); + Destroy(); + } /** Makes this pawn take damage from an attack by a_Attacker. Damage values are calculated automatically and DoTakeDamage() called */ void TakeDamage(cEntity & a_Attacker); @@ -442,9 +469,18 @@ public: virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ); /** Schedules a MoveToWorld call to occur on the next Tick of the entity */ - void ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_ShouldSetPortalCooldown = false, bool a_ShouldSendRespawn = false); + OBSOLETE void ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_ShouldSetPortalCooldown = false, bool a_ShouldSendRespawn = false) + { + LOGWARNING("ScheduleMoveToWorld is deprecated, use MoveToWorld instead"); + MoveToWorld(a_World, a_NewPosition, a_ShouldSetPortalCooldown, a_ShouldSendRespawn); + } + + bool MoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_ShouldSetPortalCooldown = false, bool a_ShouldSendRespawn = false); - bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition); + bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) + { + return MoveToWorld(a_World, a_NewPosition, false, a_ShouldSendRespawn); + } /** Moves entity to specified world, taking a world pointer */ bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn = true); @@ -454,7 +490,11 @@ public: // tolua_end - virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition); + /** Returns true if a world change is scheduled to happen. */ + bool IsWorldChangeScheduled() const + { + return (m_WorldChangeInfo.load() != nullptr); + } /** Updates clients of changes in the entity. */ virtual void BroadcastMovementUpdate(const cClientHandle * a_Exclude = nullptr); @@ -543,13 +583,16 @@ public: /** Set the entity's status to either ticking or not ticking. */ void SetIsTicking(bool a_IsTicking); - /** Adds a mob to the leashed list of mobs */ + /** Adds a mob to the leashed list of mobs. */ void AddLeashedMob(cMonster * a_Monster); - /** Removes a mob from the leashed list of mobs */ + /** Removes a mob from the leashed list of mobs. */ void RemoveLeashedMob(cMonster * a_Monster); - /** Returs whether the entity has any mob leashed to */ + /** Removes all mobs from the leashed list of mobs. */ + void RemoveAllLeashedMobs(); + + /** Returs whether the entity has any mob leashed to it. */ bool HasAnyMobLeashed() const { return m_LeashedMobs.size() > 0; } /** a lightweight calculation approach to get explosion exposure rate @@ -619,12 +662,8 @@ protected: cWorld * m_World; - /** State variables for ScheduleMoveToWorld. */ - bool m_IsWorldChangeScheduled; - bool m_WorldChangeSetPortalCooldown; - bool m_WorldChangeSendRespawn; - cWorld * m_NewWorld; - Vector3d m_NewWorldPosition; + /** If not nullptr, a world change is scheduled and a task is queued in the current world. */ + cAtomicUniquePtr m_WorldChangeInfo; /** Whether the entity is capable of taking fire or lava damage. */ bool m_IsFireproof; @@ -671,6 +710,10 @@ protected: overrides can provide further processing, such as forcing players to move at the given speed. */ virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ); + /** Handles the moving of this entity between worlds. + Should handle degenerate cases such as moving to the same world. */ + virtual void DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo); + virtual void Destroyed(void) {} // Called after the entity has been destroyed /** Applies friction to an entity @@ -689,6 +732,8 @@ protected: Only to be used when the caller will broadcast a teleport or equivalent to clients. */ virtual void ResetPosition(Vector3d a_NewPos); + /** If has any mobs are leashed, broadcasts every leashed entity to this. */ + void BroadcastLeashedMobs(); private: diff --git a/src/Entities/ExpOrb.cpp b/src/Entities/ExpOrb.cpp index ad6f6e97d..3d0c9e2b8 100644 --- a/src/Entities/ExpOrb.cpp +++ b/src/Entities/ExpOrb.cpp @@ -49,7 +49,7 @@ void cExpOrb::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) a_Player.DeltaExperience(m_Reward); m_World->BroadcastSoundEffect("entity.experience_orb.pickup", GetPosition(), 0.5f, (0.75f + (static_cast((GetUniqueID() * 23) % 32)) / 64)); - Destroy(true); + Destroy(); return true; } @@ -84,7 +84,7 @@ void cExpOrb::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_Timer += a_Dt; if (m_Timer >= std::chrono::minutes(5)) { - Destroy(true); + Destroy(); } } @@ -96,7 +96,7 @@ bool cExpOrb::DoTakeDamage(TakeDamageInfo & a_TDI) { if (a_TDI.DamageType == dtCactusContact) { - Destroy(true); + Destroy(); return true; } diff --git a/src/Entities/FallingBlock.cpp b/src/Entities/FallingBlock.cpp index 55b9e81e1..132acd09a 100644 --- a/src/Entities/FallingBlock.cpp +++ b/src/Entities/FallingBlock.cpp @@ -46,7 +46,7 @@ void cFallingBlock::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) // Fallen out of this world, just continue falling until out of sight, then destroy: if (BlockY < VOID_BOUNDARY) { - Destroy(true); + Destroy(); } return; } @@ -64,7 +64,7 @@ void cFallingBlock::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) // Fallen onto a block that breaks this into pickups (e. g. half-slab) // Must finish the fall with coords one below the block: cSandSimulator::FinishFalling(m_World, BlockX, BlockY, BlockZ, m_BlockType, m_BlockMeta); - Destroy(true); + Destroy(); return; } else if (!cSandSimulator::CanContinueFallThrough(BlockBelow)) @@ -83,14 +83,14 @@ void cFallingBlock::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { cSandSimulator::FinishFalling(m_World, BlockX, BlockY + 1, BlockZ, m_BlockType, m_BlockMeta); } - Destroy(true); + Destroy(); return; } else if ((m_BlockType == E_BLOCK_CONCRETE_POWDER) && IsBlockWater(BlockBelow)) { // Concrete powder falling into water solidifies on the first water it touches cSandSimulator::FinishFalling(m_World, BlockX, BlockY, BlockZ, E_BLOCK_CONCRETE, m_BlockMeta); - Destroy(true); + Destroy(); return; } diff --git a/src/Entities/Floater.cpp b/src/Entities/Floater.cpp index c9cc526a7..7b0314ae5 100644 --- a/src/Entities/Floater.cpp +++ b/src/Entities/Floater.cpp @@ -179,7 +179,7 @@ void cFloater::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) if (!m_World->DoWithEntityByID(m_PlayerID, [](cEntity &) { return true; })) // The owner doesn't exist anymore. Destroy the floater entity. { - Destroy(true); + Destroy(); } if (m_AttachedMobID != cEntity::INVALID_ID) diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index be64e9e2f..679e9d5ff 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -156,7 +156,7 @@ void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_Timer += a_Dt; // In case we have to destroy the pickup in the same tick. if (m_Timer > std::chrono::milliseconds(500)) { - Destroy(true); + Destroy(); return; } } @@ -180,14 +180,14 @@ void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { if (m_Timer > std::chrono::milliseconds(500)) // 0.5 second { - Destroy(true); + Destroy(); return; } } if (m_Timer > m_Lifetime) { - Destroy(true); + Destroy(); return; } } @@ -200,7 +200,7 @@ bool cPickup::DoTakeDamage(TakeDamageInfo & a_TDI) { if (a_TDI.DamageType == dtCactusContact) { - Destroy(true); + Destroy(); return true; } diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index e7b6ade15..421eddbd5 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -193,9 +193,6 @@ bool cPlayer::Initialize(OwnedEntity a_Self, cWorld & a_World) cPluginManager::Get()->CallHookSpawnedEntity(*GetWorld(), *this); - // Spawn the entity on the clients: - GetWorld()->BroadcastSpawnEntity(*this); - return true; } @@ -243,6 +240,9 @@ void cPlayer::SpawnOn(cClientHandle & a_Client) { return; } + + LOGD("Spawing %s on %s", GetName().c_str(), a_Client.GetUsername().c_str()); + a_Client.SendPlayerSpawn(*this); a_Client.SendEntityHeadLook(*this); a_Client.SendEntityEquipment(*this, 0, m_Inventory.GetEquippedItem()); @@ -1225,7 +1225,7 @@ void cPlayer::Respawn(void) if (GetWorld() != m_SpawnWorld) { - ScheduleMoveToWorld(m_SpawnWorld, GetLastBedPos(), false); + MoveToWorld(m_SpawnWorld, GetLastBedPos(), false); } else { @@ -2003,92 +2003,70 @@ void cPlayer::TossItems(const cItems & a_Items) -bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) +void cPlayer::DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) { - ASSERT(a_World != nullptr); - ASSERT(IsTicking()); + ASSERT(a_WorldChangeInfo.m_NewWorld != nullptr); - if (GetWorld() == a_World) + // Reset portal cooldown + if (a_WorldChangeInfo.m_SetPortalCooldown) { - // Don't move to same world - return false; + m_PortalCooldownData.m_TicksDelayed = 0; + m_PortalCooldownData.m_ShouldPreventTeleportation = true; } - // Ask the plugins if the player is allowed to change the world - if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World)) + if (m_World == a_WorldChangeInfo.m_NewWorld) { - // A Plugin doesn't allow the player to change the world - return false; + // Moving to same world, don't need to remove from world + SetPosition(a_WorldChangeInfo.m_NewPosition); + return; } - GetWorld()->QueueTask([this, a_World, a_ShouldSendRespawn, a_NewPosition](cWorld & a_OldWorld) - { - // The clienthandle caches the coords of the chunk we're standing at. Invalidate this. - GetClientHandle()->InvalidateCachedSentChunk(); - - // Prevent further ticking in this world - SetIsTicking(false); + LOGD("Warping player \"%s\" from world \"%s\" to \"%s\". Source chunk: (%d, %d) ", + GetName(), GetWorld()->GetName(), a_WorldChangeInfo.m_NewWorld->GetName(), + GetChunkX(), GetChunkZ() + ); - // Tell others we are gone - GetWorld()->BroadcastDestroyEntity(*this); + // Stop all mobs from targeting this player + StopEveryoneFromTargetingMe(); - // Remove player from world - // Make sure that RemovePlayer didn't return a valid smart pointer, due to the second parameter being false - // We remain valid and not destructed after this call - VERIFY(!GetWorld()->RemovePlayer(*this, false)); + // Prevent further ticking in this world + SetIsTicking(false); - // Set position to the new position - ResetPosition(a_NewPosition); - FreezeInternal(a_NewPosition, false); + // Remove from the old world + auto & OldWorld = *GetWorld(); + auto Self = OldWorld.RemovePlayer(*this); - // Stop all mobs from targeting this player - StopEveryoneFromTargetingMe(); + ResetPosition(a_WorldChangeInfo.m_NewPosition); + FreezeInternal(a_WorldChangeInfo.m_NewPosition, false); + SetWorld(a_WorldChangeInfo.m_NewWorld); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value - // Deal with new world - SetWorld(a_World); + // Set capabilities based on new world + SetCapabilities(); - // Set capabilities based on new world - SetCapabilities(); + cClientHandle * ch = GetClientHandle(); + if (ch != nullptr) + { + // The clienthandle caches the coords of the chunk we're standing at. Invalidate this. + ch->InvalidateCachedSentChunk(); - cClientHandle * ch = this->GetClientHandle(); - if (ch != nullptr) + // Send the respawn packet: + if (a_WorldChangeInfo.m_SendRespawn) { - // Send the respawn packet: - if (a_ShouldSendRespawn) - { - m_ClientHandle->SendRespawn(a_World->GetDimension()); - } - - // Update the view distance. - ch->SetViewDistance(m_ClientHandle->GetRequestedViewDistance()); - - // Send current weather of target world to player - if (a_World->GetDimension() == dimOverworld) - { - ch->SendWeather(a_World->GetWeather()); - } + ch->SendRespawn(a_WorldChangeInfo.m_NewWorld->GetDimension()); } - // Broadcast the player into the new world. - a_World->BroadcastSpawnEntity(*this); - - // Queue add to new world and removal from the old one + // Update the view distance. + ch->SetViewDistance(ch->GetRequestedViewDistance()); - // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value - cChunk * ParentChunk = this->GetParentChunk(); - - LOGD("Warping player \"%s\" from world \"%s\" to \"%s\". Source chunk: (%d, %d) ", - this->GetName().c_str(), - a_OldWorld.GetName().c_str(), a_World->GetName().c_str(), - ParentChunk->GetPosX(), ParentChunk->GetPosZ() - ); - - // New world will take over and announce client at its next tick - auto PlayerPtr = static_cast(ParentChunk->RemoveEntity(*this).release()); - a_World->AddPlayer(std::unique_ptr(PlayerPtr), &a_OldWorld); - }); + // Send current weather of target world to player + if (a_WorldChangeInfo.m_NewWorld->GetDimension() == dimOverworld) + { + ch->SendWeather(a_WorldChangeInfo.m_NewWorld->GetWeather()); + } + } - return true; + // New world will take over and announce client at its next tick + a_WorldChangeInfo.m_NewWorld->AddPlayer(std::move(Self), &OldWorld); } @@ -2515,7 +2493,7 @@ void cPlayer::HandleFloater() } m_World->DoWithEntityByID(m_FloaterID, [](cEntity & a_Entity) { - a_Entity.Destroy(true); + a_Entity.Destroy(); return true; } ); diff --git a/src/Entities/Player.h b/src/Entities/Player.h index c9249b2f1..1ce2c5d9d 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -387,10 +387,6 @@ public: void SetVisible( bool a_bVisible); // tolua_export bool IsVisible(void) const { return m_bVisible; } // tolua_export - /** Moves the player to the specified world. - Returns true if successful, false on failure (world not found). */ - virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) override; - /** Saves all player data, such as inventory, to JSON */ bool SaveToDisk(void); @@ -735,6 +731,8 @@ protected: /** The main hand of the player */ eMainHand m_MainHand; + virtual void DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) override; + /** Sets the speed and sends it to the client, so that they are forced to move so. */ virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) override; diff --git a/src/Entities/TNTEntity.cpp b/src/Entities/TNTEntity.cpp index c76b09119..fa17296cf 100644 --- a/src/Entities/TNTEntity.cpp +++ b/src/Entities/TNTEntity.cpp @@ -34,7 +34,7 @@ void cTNTEntity::SpawnOn(cClientHandle & a_ClientHandle) void cTNTEntity::Explode(void) { m_FuseTicks = 0; - Destroy(true); + Destroy(); FLOGD("BOOM at {0}", GetPosition()); m_World->DoExplosionAt(4.0, GetPosX() + 0.49, GetPosY() + 0.49, GetPosZ() + 0.49, true, esPrimedTNT, this); } diff --git a/src/Entities/WitherSkullEntity.cpp b/src/Entities/WitherSkullEntity.cpp index 65e055e59..55f1ff32f 100644 --- a/src/Entities/WitherSkullEntity.cpp +++ b/src/Entities/WitherSkullEntity.cpp @@ -43,7 +43,7 @@ void cWitherSkullEntity::OnHitEntity(cEntity & a_EntityHit, Vector3d a_HitPos) // TODO: Explode // TODO: Apply wither effect to entity and others nearby - Destroy(true); + Destroy(); } diff --git a/src/Items/ItemFishingRod.h b/src/Items/ItemFishingRod.h index 60e0617d6..2d42411e3 100644 --- a/src/Items/ItemFishingRod.h +++ b/src/Items/ItemFishingRod.h @@ -28,7 +28,7 @@ public: m_Pos = Floater.GetPosition(); m_BitePos = Floater.GetBitePos(); m_AttachedMobID = Floater.GetAttachedMobID(); - Floater.Destroy(true); + Floater.Destroy(); return true; } diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index dfcd0dd6a..09f937564 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -132,12 +132,12 @@ cMonster::~cMonster() -void cMonster::Destroy(bool a_ShouldBroadcast) +void cMonster::OnRemoveFromWorld(cWorld & a_World) { if (IsLeashed()) { cEntity * LeashedTo = GetLeashedTo(); - Unleash(false, a_ShouldBroadcast); + Unleash(false, true); // Remove leash knot if there are no more mobs leashed to if (!LeashedTo->HasAnyMobLeashed() && LeashedTo->IsLeashKnot()) @@ -146,7 +146,7 @@ void cMonster::Destroy(bool a_ShouldBroadcast) } } - super::Destroy(a_ShouldBroadcast); + super::OnRemoveFromWorld(a_World); } @@ -282,7 +282,7 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_DestroyTimer += a_Dt; if (m_DestroyTimer > std::chrono::seconds(1)) { - Destroy(true); + Destroy(); } return; } @@ -590,6 +590,20 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) +void cMonster::DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) +{ + // Stop all mobs from targeting this entity + // Stop this entity from targeting other mobs + SetTarget(nullptr); + StopEveryoneFromTargetingMe(); + + super::DoMoveToWorld(a_WorldChangeInfo); +} + + + + + void cMonster::KilledBy(TakeDamageInfo & a_TDI) { super::KilledBy(a_TDI); diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index 11d49b82e..676e8ebe5 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -43,7 +43,7 @@ public: virtual ~cMonster() override; - virtual void Destroy(bool a_ShouldBroadcast = true) override; + virtual void OnRemoveFromWorld(cWorld & a_World) override; virtual void Destroyed() override; @@ -319,6 +319,8 @@ protected: /** Adds weapon that is equipped with the chance saved in m_DropChance[...] (this will be greter than 1 if picked up or 0.085 + (0.01 per LootingLevel) if born with) to the drop */ void AddRandomWeaponDropItem(cItems & a_Drops, unsigned int a_LootingLevel); + virtual void DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) override; + private: /** A pointer to the entity this mobile is aiming to reach. The validity of this pointer SHALL be guaranteed by the pointee; diff --git a/src/NetherPortalScanner.cpp b/src/NetherPortalScanner.cpp index 9625ed7cf..78cdb2f02 100644 --- a/src/NetherPortalScanner.cpp +++ b/src/NetherPortalScanner.cpp @@ -296,7 +296,7 @@ void cNetherPortalScanner::OnDisabled(void) } FLOGD("Placing player at {0}", Position); - m_Entity->ScheduleMoveToWorld(m_World, Position, true); + m_Entity->MoveToWorld(m_World, Position, true); delete this; } diff --git a/src/OSSupport/AtomicUniquePtr.h b/src/OSSupport/AtomicUniquePtr.h new file mode 100644 index 000000000..5b18763d3 --- /dev/null +++ b/src/OSSupport/AtomicUniquePtr.h @@ -0,0 +1,81 @@ + + +#pragma once + + +/** An RAII wrapper for std::atomic. */ +template +class cAtomicUniquePtr +{ +public: + static_assert(!std::is_array::value, "cAtomicUniquePtr does not support arrays"); + DISALLOW_COPY_AND_ASSIGN(cAtomicUniquePtr); + + cAtomicUniquePtr() NOEXCEPT: + m_Ptr(nullptr) + { + } + + + cAtomicUniquePtr(std::unique_ptr a_Ptr) NOEXCEPT: + m_Ptr(a_Ptr.release()) + { + } + + cAtomicUniquePtr & operator = (std::unique_ptr a_Ptr) NOEXCEPT + { + store(std::move(a_Ptr)); + return *this; + } + + ~cAtomicUniquePtr() NOEXCEPT + { + delete load(); + } + + operator T * () const NOEXCEPT + { + return load(); + } + + bool compare_exchange_weak(T *& a_Expected, std::unique_ptr && a_Desired, std::memory_order a_Order = std::memory_order_seq_cst) NOEXCEPT + { + bool DidExchange = m_Ptr.compare_exchange_weak(a_Expected, a_Desired.get(), a_Order); + if (DidExchange) + { + // Only release ownership from the caller if the exchange occurred + a_Desired.release(); + } + return DidExchange; + } + + bool compare_exchange_strong(T *& a_Expected, std::unique_ptr && a_Desired, std::memory_order a_Order = std::memory_order_seq_cst) NOEXCEPT + { + bool DidExchange = m_Ptr.compare_exchange_strong(a_Expected, a_Desired.get(), a_Order); + if (DidExchange) + { + // Only release ownership from the caller if the exchange occurred + a_Desired.release(); + } + return DidExchange; + } + + std::unique_ptr exchange(std::unique_ptr a_Ptr, std::memory_order a_Order = std::memory_order_seq_cst) NOEXCEPT + { + return std::unique_ptr{ m_Ptr.exchange(a_Ptr.release(), a_Order) }; + } + + T * load(std::memory_order a_Order = std::memory_order_seq_cst) const NOEXCEPT + { + return m_Ptr.load(a_Order); + } + + void store(std::unique_ptr a_Ptr, std::memory_order a_Order = std::memory_order_seq_cst) NOEXCEPT + { + // Store new value and delete old value + delete m_Ptr.exchange(a_Ptr.release(), a_Order); + } + +private: + std::atomic m_Ptr; +}; diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt index 332b880ed..9f3fcb8a0 100644 --- a/src/OSSupport/CMakeLists.txt +++ b/src/OSSupport/CMakeLists.txt @@ -19,6 +19,7 @@ SET (SRCS ) SET (HDRS + AtomicUniquePtr.h CriticalSection.h Errors.h Event.h diff --git a/src/World.cpp b/src/World.cpp index 312f5967a..a80cffbcc 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -1024,6 +1024,7 @@ void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_La Entity->SetWorld(this); auto EntityPtr = Entity.get(); m_ChunkMap->AddEntity(std::move(Entity)); + EntityPtr->OnAddToWorld(*this); ASSERT(!EntityPtr->IsTicking()); EntityPtr->SetIsTicking(true); } @@ -1167,14 +1168,14 @@ void cWorld::TickMobs(std::chrono::milliseconds a_Dt) { if (Monster.GetMobType() != eMonsterType::mtWolf) { - Monster.Destroy(true); + Monster.Destroy(); } else { auto & Wolf = static_cast(Monster); if (!Wolf.IsAngry() && !Wolf.IsTame()) { - Monster.Destroy(true); + Monster.Destroy(); } } } @@ -2454,23 +2455,34 @@ void cWorld::AddPlayer(std::unique_ptr a_Player, cWorld * a_OldWorld) -std::unique_ptr cWorld::RemovePlayer(cPlayer & a_Player, bool a_RemoveFromChunk) +std::unique_ptr cWorld::RemovePlayer(cPlayer & a_Player) { - std::unique_ptr PlayerPtr; + // Check the chunkmap + std::unique_ptr PlayerPtr(static_cast(m_ChunkMap->RemoveEntity(a_Player).release())); - if (a_RemoveFromChunk) + if (PlayerPtr != nullptr) { - // To prevent iterator invalidations when an entity goes through a portal and calls this function whilst being ticked by cChunk - // we should not change cChunk's entity list if asked not to - PlayerPtr = std::unique_ptr(static_cast(m_ChunkMap->RemoveEntity(a_Player).release())); + // Player found in the world, tell it it's being removed + PlayerPtr->OnRemoveFromWorld(*this); } + else // Check the awaiting players list { cCSLock Lock(m_CSPlayersToAdd); - m_PlayersToAdd.remove_if([&](const decltype(m_PlayersToAdd)::value_type & value) -> bool + auto itr = std::find_if(m_PlayersToAdd.begin(), m_PlayersToAdd.end(), + [&](const decltype(m_PlayersToAdd)::value_type & value) + { + return (value.first.get() == &a_Player); + } + ); + + if (itr != m_PlayersToAdd.end()) { - return (value.first.get() == &a_Player); - }); + PlayerPtr = std::move(itr->first); + m_PlayersToAdd.erase(itr); + } } + + // Remove from the player list { cCSLock Lock(m_CSPlayers); LOGD("Removing player %s from world \"%s\"", a_Player.GetName().c_str(), m_WorldName.c_str()); @@ -3076,6 +3088,7 @@ OwnedEntity cWorld::RemoveEntity(cEntity & a_Entity) auto Entity = m_ChunkMap->RemoveEntity(a_Entity); if (Entity != nullptr) { + Entity->OnRemoveFromWorld(*this); return Entity; } @@ -3475,6 +3488,7 @@ void cWorld::AddQueuedPlayers(void) // Add to chunkmap, if not already there (Spawn vs MoveToWorld): auto PlayerPtr = Player.get(); m_ChunkMap->AddEntityIfNotPresent(std::move(Player)); + PlayerPtr->OnAddToWorld(*this); ASSERT(!PlayerPtr->IsTicking()); PlayerPtr->SetIsTicking(true); AddedPlayerPtrs.emplace_back(PlayerPtr, AwaitingPlayer.second); diff --git a/src/World.h b/src/World.h index 159fa7d93..4363804c6 100644 --- a/src/World.h +++ b/src/World.h @@ -263,9 +263,8 @@ public: /** Removes the player from the world. Removes the player from the addition queue, too, if appropriate. If the player has a ClientHandle, the ClientHandle is removed from all chunks in the world and will not be ticked by this world anymore. - @param a_RemoveFromChunk determines if the entity should be removed from its chunk as well. Should be false when ticking from cChunk. @return An owning reference to the given player. */ - std::unique_ptr RemovePlayer(cPlayer & a_Player, bool a_RemoveFromChunk); + std::unique_ptr RemovePlayer(cPlayer & a_Player); #ifdef _DEBUG bool IsPlayerReferencedInWorldOrChunk(cPlayer & a_Player); -- cgit v1.2.3