1
0
Fork 0

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
This commit is contained in:
Mat 2020-03-05 12:52:34 +02:00 committed by GitHub
parent d7a726a423
commit 7d4934534e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 406 additions and 222 deletions

View File

@ -3996,7 +3996,7 @@ local Hash = cCryptoHash.sha1HexString("DataToHash")
Type = "boolean",
},
},
Notes = "Removes the entity from this world and starts moving it to the specified world's spawn point. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions). <b>OBSOLETE</b>, use ScheduleMoveToWorld() instead.",
Notes = "Removes the entity from this world and starts moving it to the specified world's spawn point. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions).",
},
{
Params =
@ -4017,7 +4017,7 @@ local Hash = cCryptoHash.sha1HexString("DataToHash")
Type = "boolean",
},
},
Notes = "Removes the entity from this world and starts moving it to the specified world's spawn point. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions). <b>OBSOLETE</b>, use ScheduleMoveToWorld() instead.",
Notes = "Removes the entity from this world and starts moving it to the specified world's spawn point. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions).",
},
{
Params =
@ -4041,7 +4041,37 @@ local Hash = cCryptoHash.sha1HexString("DataToHash")
Type = "boolean",
},
},
Notes = "Removes the entity from this world and starts moving it to the specified world. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions). The Position parameter specifies the location that the entity should be placed in, in the new world. <b>OBSOLETE</b>, use ScheduleMoveToWorld() instead.",
Notes = "Removes the entity from this world and starts moving it to the specified world. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions). The Position parameter specifies the location that the entity should be placed in, in the new world.",
},
{
Params =
{
{
Name = "World",
Type = "cWorld",
},
{
Name = "Position",
Type = "Vector3d",
},
{
Name = "ShouldSetPortalCooldown",
Type = "boolean",
IsOptional = true,
},
{
Name = "ShouldSendRespawn",
Type = "boolean",
IsOptional = true,
},
},
Returns =
{
{
Type = "boolean",
},
},
Notes = "Removes the entity from this world and starts moving it to the specified world. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. If ShouldSetPortalCooldown is false (default), doesn't set any portal cooldown, if it is true, the default portal cooldown is applied to the entity. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions). The Position parameter specifies the location that the entity should be placed in, in the new world.",
},
},
ScheduleMoveToWorld =
@ -4067,7 +4097,7 @@ local Hash = cCryptoHash.sha1HexString("DataToHash")
IsOptional = true,
},
},
Notes = "Schedules a MoveToWorld call to occur on the next Tick of the entity. If ShouldSetPortalCooldown is false (default), doesn't set any portal cooldown, if it is true, the default portal cooldown is applied to the entity. If ShouldSendRespawn is false (default), no respawn packet is sent, if it is true then a respawn packet is sent to the client.",
Notes = "Schedules a MoveToWorld call to occur on the next Tick of the entity. If ShouldSetPortalCooldown is false (default), doesn't set any portal cooldown, if it is true, the default portal cooldown is applied to the entity. If ShouldSendRespawn is false (default), no respawn packet is sent, if it is true then a respawn packet is sent to the client. <b>OBSOLETE</b>, use MoveToWorld instead.",
},
SetGravity =
{

View File

@ -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)

View File

@ -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);

View File

@ -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;
}

View File

@ -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,18 +157,6 @@ bool cEntity::Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld)
cPluginManager::Get()->CallHookSpawnedEntity(a_EntityWorld, *this);
// Spawn the entity on the clients:
a_EntityWorld.BroadcastSpawnEntity(*this);
// 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;
}
@ -177,6 +164,28 @@ bool cEntity::Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld)
void cEntity::OnAddToWorld(cWorld & a_World)
{
// Spawn the entity on the clients:
m_LastSentPosition = GetPosition();
a_World.BroadcastSpawnEntity(*this);
BroadcastLeashedMobs();
}
void cEntity::OnRemoveFromWorld(cWorld & a_World)
{
RemoveAllLeashedMobs();
a_World.BroadcastDestroyEntity(*this);
}
void cEntity::WrapHeadYaw(void)
{
m_HeadYaw = NormalizeAngleDegrees(m_HeadYaw);
@ -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<cMonster*>(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<sWorldChangeInfo>();
*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;

View File

@ -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, bool a_ShouldSendRespawn, Vector3d a_NewPosition);
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)
{
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<sWorldChangeInfo> 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:

View File

@ -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<float>((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;
}

View File

@ -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;
}

View File

@ -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)

View File

@ -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;
}

View File

@ -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)
LOGD("Warping player \"%s\" from world \"%s\" to \"%s\". Source chunk: (%d, %d) ",
GetName(), GetWorld()->GetName(), a_WorldChangeInfo.m_NewWorld->GetName(),
GetChunkX(), GetChunkZ()
);
// Stop all mobs from targeting this player
StopEveryoneFromTargetingMe();
// Prevent further ticking in this world
SetIsTicking(false);
// Remove from the old world
auto & OldWorld = *GetWorld();
auto Self = OldWorld.RemovePlayer(*this);
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
// 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.
GetClientHandle()->InvalidateCachedSentChunk();
ch->InvalidateCachedSentChunk();
// Prevent further ticking in this world
SetIsTicking(false);
// Tell others we are gone
GetWorld()->BroadcastDestroyEntity(*this);
// 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));
// Set position to the new position
ResetPosition(a_NewPosition);
FreezeInternal(a_NewPosition, false);
// Stop all mobs from targeting this player
StopEveryoneFromTargetingMe();
// Deal with new world
SetWorld(a_World);
// Set capabilities based on new world
SetCapabilities();
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);
// Update the view distance.
ch->SetViewDistance(ch->GetRequestedViewDistance());
// Queue add to new world and removal from the old one
// Send current weather of target world to player
if (a_WorldChangeInfo.m_NewWorld->GetDimension() == dimOverworld)
{
ch->SendWeather(a_WorldChangeInfo.m_NewWorld->GetWeather());
}
}
// 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<cPlayer *>(ParentChunk->RemoveEntity(*this).release());
a_World->AddPlayer(std::unique_ptr<cPlayer>(PlayerPtr), &a_OldWorld);
});
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;
}
);

View File

@ -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;

View File

@ -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);
}

View File

@ -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();
}

View File

@ -28,7 +28,7 @@ public:
m_Pos = Floater.GetPosition();
m_BitePos = Floater.GetBitePos();
m_AttachedMobID = Floater.GetAttachedMobID();
Floater.Destroy(true);
Floater.Destroy();
return true;
}

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -0,0 +1,81 @@
#pragma once
/** An RAII wrapper for std::atomic<T*>. */
template <typename T>
class cAtomicUniquePtr
{
public:
static_assert(!std::is_array<T>::value, "cAtomicUniquePtr does not support arrays");
DISALLOW_COPY_AND_ASSIGN(cAtomicUniquePtr);
cAtomicUniquePtr() NOEXCEPT:
m_Ptr(nullptr)
{
}
cAtomicUniquePtr(std::unique_ptr<T> a_Ptr) NOEXCEPT:
m_Ptr(a_Ptr.release())
{
}
cAtomicUniquePtr & operator = (std::unique_ptr<T> 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<T> && 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<T> && 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<T> exchange(std::unique_ptr<T> a_Ptr, std::memory_order a_Order = std::memory_order_seq_cst) NOEXCEPT
{
return std::unique_ptr<T>{ 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<T> 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<T*> m_Ptr;
};

View File

@ -19,6 +19,7 @@ SET (SRCS
)
SET (HDRS
AtomicUniquePtr.h
CriticalSection.h
Errors.h
Event.h

View File

@ -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<cWolf &>(Monster);
if (!Wolf.IsAngry() && !Wolf.IsTame())
{
Monster.Destroy(true);
Monster.Destroy();
}
}
}
@ -2454,23 +2455,34 @@ void cWorld::AddPlayer(std::unique_ptr<cPlayer> a_Player, cWorld * a_OldWorld)
std::unique_ptr<cPlayer> cWorld::RemovePlayer(cPlayer & a_Player, bool a_RemoveFromChunk)
std::unique_ptr<cPlayer> cWorld::RemovePlayer(cPlayer & a_Player)
{
std::unique_ptr<cPlayer> PlayerPtr;
// Check the chunkmap
std::unique_ptr<cPlayer> PlayerPtr(static_cast<cPlayer *>(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<cPlayer>(static_cast<cPlayer *>(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);

View File

@ -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<cPlayer> RemovePlayer(cPlayer & a_Player, bool a_RemoveFromChunk);
std::unique_ptr<cPlayer> RemovePlayer(cPlayer & a_Player);
#ifdef _DEBUG
bool IsPlayerReferencedInWorldOrChunk(cPlayer & a_Player);