diff options
51 files changed, 2989 insertions, 3551 deletions
diff --git a/src/Bindings/DeprecatedBindings.cpp b/src/Bindings/DeprecatedBindings.cpp index 0ffc58cbb..4f610eb78 100644 --- a/src/Bindings/DeprecatedBindings.cpp +++ b/src/Bindings/DeprecatedBindings.cpp @@ -291,6 +291,126 @@ tolua_lerror: +/* function: TeleportToCoords */ +static int tolua_cEntity_TeleportToCoords00(lua_State* tolua_S) +{ + cLuaState LuaState(tolua_S); + + if ( + !LuaState.CheckParamUserType(1, "cEntity") || + !LuaState.CheckParamNumber(2, 4) || + !LuaState.CheckParamEnd(5) + ) + return 0; + else + { + cEntity * self = nullptr; + double BlockX = 0; + double BlockY = 0; + double BlockZ = 0; + + LuaState.GetStackValues(1, self, BlockX, BlockY, BlockZ); + + #ifndef TOLUA_RELEASE + if (self == nullptr) + { + tolua_error(LuaState, "invalid 'self' in function 'TeleportToCoords'", nullptr); + } + #endif + { + self->TeleportToCoords({ BlockX, BlockY, BlockZ }); + } + } + LOGWARNING("Warning in function call 'TeleportToCoords': TeleportToCoords(X, Y, Z) is deprecated. Please use TeleportToCoords(Vector3d)"); + LuaState.LogStackTrace(0); + return 1; +} + + + + + +/* function: MoveToWorld */ +static int tolua_cEntity_MoveToWorld00(lua_State* tolua_S) +{ + cLuaState LuaState(tolua_S); + + if (!LuaState.CheckParamUserType(1, "cEntity")) + { + return 0; + } + + if (LuaState.CheckParamString(2) && (LuaState.CheckParamEnd(3) || LuaState.CheckParamEnd(4))) + { + cEntity * self = nullptr; + std::string WorldName; + LuaState.GetStackValues(1, self, WorldName); + + #ifndef TOLUA_RELEASE + if (self == nullptr) + { + tolua_error(LuaState, "invalid 'self' in function 'MoveToWorld'", nullptr); + } + #endif + { + self->MoveToWorld(WorldName, self->GetPosition()); + } + } + else if (LuaState.CheckParamUserType(2, "cWorld")) + { + if (LuaState.CheckParamUserType(4, "Vector3d") && LuaState.CheckParamEnd(5)) + { + cEntity * self = nullptr; + cWorld * World; + Vector3d * Position; + LuaState.GetStackValues(1, self, World); + LuaState.GetStackValues(4, Position); + + #ifndef TOLUA_RELEASE + if (self == nullptr) + { + tolua_error(LuaState, "invalid 'self' in function 'MoveToWorld'", nullptr); + } + #endif + { + self->MoveToWorld(*World, *Position); + } + } + else if (LuaState.CheckParamEnd(4)) + { + cEntity * self = nullptr; + std::string WorldName; + LuaState.GetStackValues(1, self, WorldName); + + #ifndef TOLUA_RELEASE + if (self == nullptr) + { + tolua_error(LuaState, "invalid 'self' in function 'MoveToWorld'", nullptr); + } + #endif + { + self->MoveToWorld(WorldName, self->GetPosition()); + } + } + else + { + return 0; + } + } + else + { + return 0; + } + + LOGWARNING("Warning in function call 'MoveToWorld': the ShouldSendRespawn parameter is deprecated and a destination position is mandatory."); + LuaState.LogStackTrace(0); + return 1; +} + + + + + /** function: cWorld:SetSignLines */ static int tolua_cWorld_SetSignLines(lua_State * tolua_S) { @@ -416,6 +536,11 @@ void DeprecatedBindings::Bind(lua_State * tolua_S) tolua_function(tolua_S,"abs", tolua_Vector3_Abs<double>); tolua_function(tolua_S,"clamp", tolua_Vector3_Clamp<double>); tolua_endmodule(tolua_S); + + tolua_beginmodule(tolua_S, "cEntity"); + tolua_function(tolua_S, "MoveToWorld", tolua_cEntity_MoveToWorld00); + tolua_function(tolua_S, "TeleportToCoords", tolua_cEntity_TeleportToCoords00); + tolua_endmodule(tolua_S); tolua_endmodule(tolua_S); } diff --git a/src/Bindings/ManualBindings_World.cpp b/src/Bindings/ManualBindings_World.cpp index 56a4ee65b..b101fdd7d 100644 --- a/src/Bindings/ManualBindings_World.cpp +++ b/src/Bindings/ManualBindings_World.cpp @@ -450,9 +450,6 @@ static int tolua_cWorld_PrepareChunk(lua_State * tolua_S) { m_LuaState.Call(m_Callback, a_CBChunkX, a_CBChunkZ, a_IsSuccess); } - - // This is the last reference of this object, we must delete it so that it doesn't leak: - delete this; } protected: diff --git a/src/BlockEntities/ChestEntity.h b/src/BlockEntities/ChestEntity.h index 8953644e6..0b499350f 100644 --- a/src/BlockEntities/ChestEntity.h +++ b/src/BlockEntities/ChestEntity.h @@ -66,7 +66,7 @@ private: GetWindow()->BroadcastWholeWindow(); } - m_World->MarkChunkDirty(GetChunkX(), GetChunkZ()); + m_World->QueueTask([this](cWorld & a_World) { a_World.MarkChunkDirty(GetChunkX(), GetChunkZ()); }); } } diff --git a/src/Broadcaster.cpp b/src/Broadcaster.cpp index 594d12208..77091ff1c 100644 --- a/src/Broadcaster.cpp +++ b/src/Broadcaster.cpp @@ -15,13 +15,14 @@ void cBroadcaster::BroadcastParticleEffect(const AString & a_ParticleName, const m_World->DoWithChunkAt(a_Src, [=](cChunk & a_Chunk) -> bool { - for (auto && client : a_Chunk.GetAllClients()) + for (const auto & ClientHandle : a_Chunk.GetAllStrongClientPtrs()) { - if (client == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - client->SendParticleEffect(a_ParticleName, a_Src.x, a_Src.y, a_Src.z, a_Offset.x, a_Offset.y, a_Offset.z, a_ParticleData, a_ParticleAmount); + + ClientHandle->SendParticleEffect(a_ParticleName, a_Src.x, a_Src.y, a_Src.z, a_Offset.x, a_Offset.y, a_Offset.z, a_ParticleData, a_ParticleAmount); }; return true; }); @@ -33,13 +34,14 @@ void cBroadcaster::BroadcastParticleEffect(const AString & a_ParticleName, const m_World->DoWithChunkAt(a_Src, [=](cChunk & a_Chunk) -> bool { - for (auto && client : a_Chunk.GetAllClients()) + for (const auto & ClientHandle : a_Chunk.GetAllStrongClientPtrs()) { - if (client == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - client->SendParticleEffect(a_ParticleName, a_Src, a_Offset, a_ParticleData, a_ParticleAmount, a_Data); + + ClientHandle->SendParticleEffect(a_ParticleName, a_Src, a_Offset, a_ParticleData, a_ParticleAmount, a_Data); }; return true; }); diff --git a/src/ByteBuffer.cpp b/src/ByteBuffer.cpp index b5f862a73..010cc6690 100644 --- a/src/ByteBuffer.cpp +++ b/src/ByteBuffer.cpp @@ -136,7 +136,7 @@ bool cByteBuffer::Write(const void * a_Bytes, size_t a_Count) // Store the current free space for a check after writing: size_t CurFreeSpace = GetFreeSpace(); #ifdef _DEBUG - size_t CurReadableSpace = GetReadableSpace(); + auto CurReadableSpace = GetReadableSpace(); #endif size_t WrittenBytes = 0; diff --git a/src/Chunk.cpp b/src/Chunk.cpp index 06d5eb319..b5c2339b7 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -200,10 +200,7 @@ void cChunk::MarkRegenerating(void) SetPresence(cpQueued); // Tell all clients attached to this chunk that they want this chunk: - for (auto ClientHandle : m_LoadedByClient) - { - ClientHandle->AddWantedChunk(m_PosX, m_PosZ); - } // for itr - m_LoadedByClient[] + GetWorld()->GetChunkSender().QueueSendChunkTo(GetPosX(), GetPosZ(), cChunkSender::E_CHUNK_PRIORITY_MEDIUM, GetAllWeakClientPtrs()); } @@ -213,10 +210,12 @@ void cChunk::MarkRegenerating(void) bool cChunk::CanUnload(void) { return - m_LoadedByClient.empty() && // The chunk is not used by any client + m_LoadedByClient.empty() && // The chunk is not loaded by any client !m_IsDirty && // The chunk has been saved properly or hasn't been touched since the load / gen (m_StayCount == 0) && // The chunk is not in a ChunkStay - (m_Presence != cpQueued) ; // The chunk is not queued for loading / generating (otherwise multi-load / multi-gen could occur) + (m_Presence != cpQueued) && // The chunk is not queued for loading / generating (otherwise multi-load / multi-gen could occur) + (std::find_if(m_Entities.cbegin(), m_Entities.cend(), [](const decltype(m_Entities)::value_type & a_Value) { return a_Value->IsPlayer(); }) == m_Entities.cend()) // The chunk does not contain a player + ; } @@ -477,7 +476,7 @@ void cChunk::CollectMobCensus(cMobCensus & toFill) toFill.CollectSpawnableChunk(*this); std::vector<Vector3d> PlayerPositions; PlayerPositions.reserve(m_LoadedByClient.size()); - for (auto ClientHandle : m_LoadedByClient) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { const cPlayer * currentPlayer = ClientHandle->GetPlayer(); PlayerPositions.push_back(currentPlayer->GetPosition()); @@ -752,15 +751,12 @@ void cChunk::BroadcastPendingBlockChanges(void) if (m_PendingSendBlocks.size() >= 10240) { // Resend the full chunk - for (auto ClientHandle : m_LoadedByClient) - { - m_World->ForceSendChunkTo(m_PosX, m_PosZ, cChunkSender::E_CHUNK_PRIORITY_MEDIUM, ClientHandle); - } + m_World->GetChunkSender().QueueSendChunkTo(m_PosX, m_PosZ, cChunkSender::E_CHUNK_PRIORITY_MEDIUM, m_LoadedByClient); } else { // Only send block changes - for (auto ClientHandle : m_LoadedByClient) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { ClientHandle->SendBlockChanges(m_PosX, m_PosZ, m_PendingSendBlocks); } @@ -1637,25 +1633,35 @@ void cChunk::FastSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockT -void cChunk::SendBlockTo(int a_RelX, int a_RelY, int a_RelZ, cClientHandle * a_Client) +void cChunk::SendBlockTo(int a_RelX, int a_RelY, int a_RelZ, const std::weak_ptr<cClientHandle> & a_Client) { - - if (a_Client == nullptr) + auto Client = a_Client.lock(); + if (Client == nullptr) + { + return; + } + + if ( + std::find_if( + m_LoadedByClient.cbegin(), + m_LoadedByClient.cend(), + std::bind(std::equal_to<decltype(m_LoadedByClient)::value_type>(), a_Client, std::placeholders::_1) + ) == m_LoadedByClient.cend() + ) { - // Queue the block for all clients in the chunk (will be sent in Tick()) - m_PendingSendBlocks.push_back(sSetBlock(m_PosX, m_PosZ, a_RelX, a_RelY, a_RelZ, GetBlock(a_RelX, a_RelY, a_RelZ), GetMeta(a_RelX, a_RelY, a_RelZ))); + // Do not send block changes in chunks that weren't sent (or are soon to be sent) to the client yet return; } Vector3i wp = PositionToWorldPosition(a_RelX, a_RelY, a_RelZ); - a_Client->SendBlockChange(wp.x, wp.y, wp.z, GetBlock(a_RelX, a_RelY, a_RelZ), GetMeta(a_RelX, a_RelY, a_RelZ)); + Client->SendBlockChange(wp.x, wp.y, wp.z, GetBlock(a_RelX, a_RelY, a_RelZ), GetMeta(a_RelX, a_RelY, a_RelZ)); // FS #268 - if a BlockEntity digging is cancelled by a plugin, the entire block entity must be re-sent to the client: for (cBlockEntityList::iterator itr = m_BlockEntities.begin(), end = m_BlockEntities.end(); itr != end; ++itr) { if (((*itr)->GetPosX() == wp.x) && ((*itr)->GetPosY() == wp.y) && ((*itr)->GetPosZ() == wp.z)) { - (*itr)->SendTo(*a_Client); + (*itr)->SendTo(*Client); } } // for itr - m_BlockEntities } @@ -1726,6 +1732,35 @@ void cChunk::SetAlwaysTicked(bool a_AlwaysTicked) +std::vector<std::shared_ptr<cClientHandle>> cChunk::GetAllStrongClientPtrs(void) +{ + std::vector<std::shared_ptr<cClientHandle>> Clients; + m_LoadedByClient.erase( + std::remove_if( + m_LoadedByClient.begin(), + m_LoadedByClient.end(), + [&Clients](const auto & a_Value) + { + auto ClientHandle = a_Value.lock(); + if (ClientHandle == nullptr) + { + return true; + } + + Clients.emplace_back(ClientHandle); + return false; + } + ), + m_LoadedByClient.end() + ); + + return Clients; +} + + + + + bool cChunk::UseBlockEntity(cPlayer * a_Player, int a_X, int a_Y, int a_Z) { cBlockEntity * be = GetBlockEntity(a_X, a_Y, a_Z); @@ -1762,10 +1797,7 @@ void cChunk::SetAreaBiome(int a_MinRelX, int a_MaxRelX, int a_MinRelZ, int a_Max MarkDirty(); // Re-send the chunk to all clients: - for (auto ClientHandle : m_LoadedByClient) - { - m_World->ForceSendChunkTo(m_PosX, m_PosZ, cChunkSender::E_CHUNK_PRIORITY_MEDIUM, ClientHandle); - } // for itr - m_LoadedByClient[] + m_World->GetChunkSender().QueueSendChunkTo(m_PosX, m_PosZ, cChunkSender::E_CHUNK_PRIORITY_MEDIUM, m_LoadedByClient); } @@ -1858,17 +1890,23 @@ void cChunk::RemoveBlockEntity(cBlockEntity * a_BlockEntity) -bool cChunk::AddClient(cClientHandle * a_Client) +bool cChunk::AddClient(const std::shared_ptr<cClientHandle> & a_Client) { - if (std::find(m_LoadedByClient.begin(), m_LoadedByClient.end(), a_Client) != m_LoadedByClient.end()) + if ( + std::find_if( + m_LoadedByClient.cbegin(), + m_LoadedByClient.cend(), + std::bind(std::equal_to<decltype(m_LoadedByClient)::value_type>(), a_Client, std::placeholders::_1) // Alternatively, std::bind1st, but you know, it's deprecated + ) != m_LoadedByClient.end() + ) { // Already there, nothing needed return false; } - m_LoadedByClient.push_back(a_Client); + m_LoadedByClient.emplace_back(a_Client); - for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end(); ++itr) + for (const auto & Entity : m_Entities) { /* // DEBUG: @@ -1878,7 +1916,12 @@ bool cChunk::AddClient(cClientHandle * a_Client) a_Client->GetUsername().c_str() ); */ - (*itr)->SpawnOn(*a_Client); + Entity->SpawnOn(*a_Client); + } + + for (const auto & BlockEntity : m_BlockEntities) + { + BlockEntity->SendTo(*a_Client); } return true; } @@ -1887,39 +1930,31 @@ bool cChunk::AddClient(cClientHandle * a_Client) -void cChunk::RemoveClient(cClientHandle * a_Client) +void cChunk::RemoveClient(const std::shared_ptr<cClientHandle> & a_Client) { - auto itr = std::remove(m_LoadedByClient.begin(), m_LoadedByClient.end(), a_Client); + auto itr = std::remove_if( + m_LoadedByClient.begin(), + m_LoadedByClient.end(), + std::bind(std::equal_to<decltype(m_LoadedByClient)::value_type>(), a_Client, std::placeholders::_1) + ); + // We should always remove at most one client. ASSERT(std::distance(itr, m_LoadedByClient.end()) <= 1); + // Note: itr can equal m_LoadedByClient.end() m_LoadedByClient.erase(itr, m_LoadedByClient.end()); - if (!a_Client->IsDestroyed()) + for (const auto & Entity : m_Entities) { - for (auto Entity : m_Entities) - { - /* - // DEBUG: - LOGD("chunk [%i, %i] destroying entity #%i for player \"%s\"", - m_PosX, m_PosZ, - (*itr)->GetUniqueID(), a_Client->GetUsername().c_str() - ); - */ - a_Client->SendDestroyEntity(*Entity); - } + /* + // DEBUG: + LOGD("chunk [%i, %i] destroying entity #%i for player \"%s\"", + m_PosX, m_PosZ, + (*itr)->GetUniqueID(), a_Client->GetUsername().c_str() + ); + */ + a_Client->SendDestroyEntity(*Entity); } - - return; -} - - - - - -bool cChunk::HasClient(cClientHandle * a_Client) -{ - return std::find(m_LoadedByClient.begin(), m_LoadedByClient.end(), a_Client) != m_LoadedByClient.end(); } @@ -2037,15 +2072,14 @@ bool cChunk::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_ bool cChunk::DoWithEntityByID(UInt32 a_EntityID, cEntityCallback & a_Callback, bool & a_CallbackResult) { - // The entity list is locked by the parent chunkmap's CS - for (cEntityList::iterator itr = m_Entities.begin(), end = m_Entities.end(); itr != end; ++itr) + for (auto & Entity : m_Entities) { - if (((*itr)->GetUniqueID() == a_EntityID) && ((*itr)->IsTicking())) + if ((Entity->GetUniqueID() == a_EntityID) && Entity->IsTicking()) { - a_CallbackResult = a_Callback.Item(*itr); + a_CallbackResult = a_Callback.Item(Entity.get()); return true; } - } // for itr - m_Entitites[] + } return false; } @@ -2059,7 +2093,7 @@ bool cChunk::ForEachBlockEntity(cBlockEntityCallback & a_Callback) for (cBlockEntityList::iterator itr = m_BlockEntities.begin(), itr2 = itr; itr != m_BlockEntities.end(); itr = itr2) { ++itr2; - if (a_Callback.Item(*itr)) + if (a_Callback.Item((*itr).get())) { return false; } @@ -2821,10 +2855,10 @@ cChunk * cChunk::GetRelNeighborChunkAdjustCoords(int & a_RelX, int & a_RelZ) con void cChunk::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle) { - for (auto ClientHandle : m_LoadedByClient) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { ClientHandle->SendAttachEntity(a_Entity, a_Vehicle); - } // for itr - LoadedByClient[] + } } @@ -2833,14 +2867,15 @@ void cChunk::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity & a_V void cChunk::BroadcastBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendBlockAction(a_BlockX, a_BlockY, a_BlockZ, a_Byte1, a_Byte2, a_BlockType); - } // for itr - LoadedByClient[] + + ClientHandle->SendBlockAction(a_BlockX, a_BlockY, a_BlockZ, a_Byte1, a_Byte2, a_BlockType); + } } @@ -2849,14 +2884,15 @@ void cChunk::BroadcastBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, char void cChunk::BroadcastBlockBreakAnimation(UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendBlockBreakAnim(a_EntityID, a_BlockX, a_BlockY, a_BlockZ, a_Stage); - } // for itr - LoadedByClient[] + + ClientHandle->SendBlockBreakAnim(a_EntityID, a_BlockX, a_BlockY, a_BlockZ, a_Stage); + } } @@ -2871,14 +2907,16 @@ void cChunk::BroadcastBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cons { return; } - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - Entity->SendTo(*(*itr)); - } // for itr - LoadedByClient[] + + Entity->SendTo(*ClientHandle); + } } @@ -2887,14 +2925,15 @@ void cChunk::BroadcastBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cons void cChunk::BroadcastCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendCollectEntity(a_Entity, a_Player); - } // for itr - LoadedByClient[] + + ClientHandle->SendCollectEntity(a_Entity, a_Player); + } } @@ -2903,14 +2942,15 @@ void cChunk::BroadcastCollectEntity(const cEntity & a_Entity, const cPlayer & a_ void cChunk::BroadcastDestroyEntity(const cEntity & a_Entity, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendDestroyEntity(a_Entity); - } // for itr - LoadedByClient[] + + ClientHandle->SendDestroyEntity(a_Entity); + } } @@ -2919,7 +2959,7 @@ void cChunk::BroadcastDestroyEntity(const cEntity & a_Entity, const cClientHandl void cChunk::BroadcastDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle) { - for (auto ClientHandle : m_LoadedByClient) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { ClientHandle->SendDetachEntity(a_Entity, a_PreviousVehicle); } // for itr - LoadedByClient[] @@ -2931,14 +2971,15 @@ void cChunk::BroadcastDetachEntity(const cEntity & a_Entity, const cEntity & a_P void cChunk::BroadcastEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendEntityEffect(a_Entity, a_EffectID, a_Amplifier, a_Duration); - } // for itr - LoadedByClient[] + + ClientHandle->SendEntityEffect(a_Entity, a_EffectID, a_Amplifier, a_Duration); + } } @@ -2947,14 +2988,15 @@ void cChunk::BroadcastEntityEffect(const cEntity & a_Entity, int a_EffectID, int void cChunk::BroadcastEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendEntityEquipment(a_Entity, a_SlotNum, a_Item); - } // for itr - LoadedByClient[] + + ClientHandle->SendEntityEquipment(a_Entity, a_SlotNum, a_Item); + } } @@ -2963,14 +3005,15 @@ void cChunk::BroadcastEntityEquipment(const cEntity & a_Entity, short a_SlotNum, void cChunk::BroadcastEntityHeadLook(const cEntity & a_Entity, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendEntityHeadLook(a_Entity); - } // for itr - LoadedByClient[] + + ClientHandle->SendEntityHeadLook(a_Entity); + } } @@ -2979,14 +3022,15 @@ void cChunk::BroadcastEntityHeadLook(const cEntity & a_Entity, const cClientHand void cChunk::BroadcastEntityLook(const cEntity & a_Entity, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendEntityLook(a_Entity); - } // for itr - LoadedByClient[] + + ClientHandle->SendEntityLook(a_Entity); + } } @@ -2995,14 +3039,15 @@ void cChunk::BroadcastEntityLook(const cEntity & a_Entity, const cClientHandle * void cChunk::BroadcastEntityMetadata(const cEntity & a_Entity, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendEntityMetadata(a_Entity); - } // for itr - LoadedByClient[] + + ClientHandle->SendEntityMetadata(a_Entity); + } } @@ -3011,14 +3056,15 @@ void cChunk::BroadcastEntityMetadata(const cEntity & a_Entity, const cClientHand void cChunk::BroadcastEntityRelMove(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendEntityRelMove(a_Entity, a_RelX, a_RelY, a_RelZ); - } // for itr - LoadedByClient[] + + ClientHandle->SendEntityRelMove(a_Entity, a_RelX, a_RelY, a_RelZ); + } } @@ -3027,14 +3073,15 @@ void cChunk::BroadcastEntityRelMove(const cEntity & a_Entity, char a_RelX, char void cChunk::BroadcastEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendEntityRelMoveLook(a_Entity, a_RelX, a_RelY, a_RelZ); - } // for itr - LoadedByClient[] + + ClientHandle->SendEntityRelMoveLook(a_Entity, a_RelX, a_RelY, a_RelZ); + } } @@ -3043,14 +3090,15 @@ void cChunk::BroadcastEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, c void cChunk::BroadcastEntityStatus(const cEntity & a_Entity, char a_Status, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendEntityStatus(a_Entity, a_Status); - } // for itr - LoadedByClient[] + + ClientHandle->SendEntityStatus(a_Entity, a_Status); + } } @@ -3059,14 +3107,15 @@ void cChunk::BroadcastEntityStatus(const cEntity & a_Entity, char a_Status, cons void cChunk::BroadcastEntityVelocity(const cEntity & a_Entity, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendEntityVelocity(a_Entity); - } // for itr - LoadedByClient[] + + ClientHandle->SendEntityVelocity(a_Entity); + } } @@ -3075,14 +3124,15 @@ void cChunk::BroadcastEntityVelocity(const cEntity & a_Entity, const cClientHand void cChunk::BroadcastEntityAnimation(const cEntity & a_Entity, char a_Animation, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendEntityAnimation(a_Entity, a_Animation); - } // for itr - LoadedByClient[] + + ClientHandle->SendEntityAnimation(a_Entity, a_Animation); + } } @@ -3091,14 +3141,15 @@ void cChunk::BroadcastEntityAnimation(const cEntity & a_Entity, char a_Animation void cChunk::BroadcastParticleEffect(const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount, cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendParticleEffect(a_ParticleName, a_SrcX, a_SrcY, a_SrcZ, a_OffsetX, a_OffsetY, a_OffsetZ, a_ParticleData, a_ParticleAmount); - } // for itr - LoadedByClient[] + + ClientHandle->SendParticleEffect(a_ParticleName, a_SrcX, a_SrcY, a_SrcZ, a_OffsetX, a_OffsetY, a_OffsetZ, a_ParticleData, a_ParticleAmount); + } } @@ -3107,14 +3158,15 @@ void cChunk::BroadcastParticleEffect(const AString & a_ParticleName, float a_Src void cChunk::BroadcastRemoveEntityEffect(const cEntity & a_Entity, int a_EffectID, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendRemoveEntityEffect(a_Entity, a_EffectID); - } // for itr - LoadedByClient[] + + ClientHandle->SendRemoveEntityEffect(a_Entity, a_EffectID); + } } @@ -3123,14 +3175,15 @@ void cChunk::BroadcastRemoveEntityEffect(const cEntity & a_Entity, int a_EffectI void cChunk::BroadcastSoundEffect(const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendSoundEffect(a_SoundName, a_X, a_Y, a_Z, a_Volume, a_Pitch); - } // for itr - LoadedByClient[] + + ClientHandle->SendSoundEffect(a_SoundName, a_X, a_Y, a_Z, a_Volume, a_Pitch); + } } @@ -3139,14 +3192,15 @@ void cChunk::BroadcastSoundEffect(const AString & a_SoundName, double a_X, doubl void cChunk::BroadcastSoundParticleEffect(const EffectID a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendSoundParticleEffect(a_EffectID, a_SrcX, a_SrcY, a_SrcZ, a_Data); - } // for itr - LoadedByClient[] + + ClientHandle->SendSoundParticleEffect(a_EffectID, a_SrcX, a_SrcY, a_SrcZ, a_Data); + } } @@ -3155,14 +3209,15 @@ void cChunk::BroadcastSoundParticleEffect(const EffectID a_EffectID, int a_SrcX, void cChunk::BroadcastSpawnEntity(cEntity & a_Entity, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - a_Entity.SpawnOn(*(*itr)); - } // for itr - LoadedByClient[] + + a_Entity.SpawnOn(*ClientHandle); + } } @@ -3171,14 +3226,15 @@ void cChunk::BroadcastSpawnEntity(cEntity & a_Entity, const cClientHandle * a_Ex void cChunk::BroadcastThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - if (*itr == a_Exclude) + if (ClientHandle.get() == a_Exclude) { continue; } - (*itr)->SendThunderbolt(a_BlockX, a_BlockY, a_BlockZ); - } // for itr - LoadedByClient[] + + ClientHandle->SendThunderbolt(a_BlockX, a_BlockY, a_BlockZ); + } } @@ -3187,10 +3243,10 @@ void cChunk::BroadcastThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ, cons void cChunk::BroadcastUseBed(const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) { - for (auto itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) + for (const auto & ClientHandle : GetAllStrongClientPtrs()) { - (*itr)->SendUseBed(a_Entity, a_BlockX, a_BlockY, a_BlockZ); - } // for itr - LoadedByClient[] + ClientHandle->SendUseBed(a_Entity, a_BlockX, a_BlockY, a_BlockZ); + } } diff --git a/src/Chunk.h b/src/Chunk.h index 925680fdd..de46b5f8f 100644 --- a/src/Chunk.h +++ b/src/Chunk.h @@ -88,7 +88,7 @@ public: cChunk * a_NeighborXM, cChunk * a_NeighborXP, cChunk * a_NeighborZM, cChunk * a_NeighborZP, // Neighbor chunks cAllocationPool<cChunkData::sChunkSection> & a_Pool ); - cChunk(cChunk & other); + ~cChunk(); /** Returns true iff the chunk block data is valid (loaded / generated) */ @@ -244,22 +244,21 @@ public: int GetHeight( int a_X, int a_Z); - void SendBlockTo(int a_RelX, int a_RelY, int a_RelZ, cClientHandle * a_Client); + void SendBlockTo(int a_RelX, int a_RelY, int a_RelZ, const std::weak_ptr<cClientHandle> & a_Client); /** Adds a client to the chunk; returns true if added, false if already there */ - bool AddClient(cClientHandle * a_Client); + bool AddClient(const std::shared_ptr<cClientHandle> & a_Client); /** Removes the specified client from the chunk; ignored if client not in chunk. */ - void RemoveClient(cClientHandle * a_Client); - - /** Returns true if the specified client is present in this chunk. */ - bool HasClient(cClientHandle * a_Client); + void RemoveClient(const std::shared_ptr<cClientHandle> & a_Client); /** Returns true if theres any client in the chunk; false otherwise */ bool HasAnyClients(void) const; - void AddEntity(cEntity * a_Entity); - void RemoveEntity(cEntity * a_Entity); + void AddEntity(std::unique_ptr<cEntity> a_Entity); + + void RemoveEntity(cEntity & a_Entity); + bool HasEntity(UInt32 a_EntityID); /** Calls the callback for each entity; returns true if all entities processed, false if the callback aborted by returning true */ @@ -477,10 +476,13 @@ public: as at least one requests is active the chunk will be ticked). */ void SetAlwaysTicked(bool a_AlwaysTicked); - // Makes a copy of the list - cClientHandleList GetAllClients(void) const + /** Collects and returns a list of all clients in the world who currently have this chunk. */ + std::vector<std::shared_ptr<cClientHandle>> GetAllStrongClientPtrs(void); + + /** Returns the internal data structure holding weak pointers to all clients in this chunk. */ + auto & GetAllWeakClientPtrs(void) { - return cClientHandleList(m_LoadedByClient.begin(), m_LoadedByClient.end()); + return m_LoadedByClient; } private: diff --git a/src/ChunkDef.h b/src/ChunkDef.h index b3cf9049f..d146fe9ac 100644 --- a/src/ChunkDef.h +++ b/src/ChunkDef.h @@ -53,6 +53,32 @@ typedef unsigned char HEIGHTTYPE; + +class cChunkCoords +{ +public: + int m_ChunkX; + int m_ChunkZ; + + cChunkCoords(int a_ChunkX, int a_ChunkZ) : m_ChunkX(a_ChunkX), m_ChunkZ(a_ChunkZ) {} + + bool operator == (const cChunkCoords & a_Other) const + { + return ((m_ChunkX == a_Other.m_ChunkX) && (m_ChunkZ == a_Other.m_ChunkZ)); + } + + bool operator != (const cChunkCoords & a_Other) const + { + return !operator == (a_Other); + } +}; + +typedef std::vector<cChunkCoords> cChunkCoordsVector; + + + + + /** Constants used throughout the code, useful typedefs and utility functions */ class cChunkDef { @@ -142,6 +168,12 @@ public: } } + /** Converts absolute block coords to chunk coords. */ + inline static cChunkCoords BlockToChunk(const Vector3i & a_Position) + { + return { FloorC(a_Position.x / cChunkDef::Width), FloorC(a_Position.x / cChunkDef::Width) }; + } + inline static int MakeIndex(int x, int y, int z) { @@ -411,27 +443,6 @@ typedef std::vector<sSetBlock> sSetBlockVector; -class cChunkCoords -{ -public: - int m_ChunkX; - int m_ChunkZ; - - cChunkCoords(int a_ChunkX, int a_ChunkZ) : m_ChunkX(a_ChunkX), m_ChunkZ(a_ChunkZ) {} - - bool operator == (const cChunkCoords & a_Other) const - { - return ((m_ChunkX == a_Other.m_ChunkX) && (m_ChunkZ == a_Other.m_ChunkZ)); - } -} ; - -typedef std::list<cChunkCoords> cChunkCoordsList; -typedef std::vector<cChunkCoords> cChunkCoordsVector; - - - - - /** A simple hash function for chunk coords, we assume that chunk coords won't use more than 16 bits, so the hash is almost an identity. Used for std::unordered_map<cChunkCoords, ...> */ class cChunkCoordsHash diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index 7c4162b25..7f53865d0 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -65,6 +65,8 @@ cChunkMap::~cChunkMap() cChunkPtr cChunkMap::ConstructChunk(int a_ChunkX, int a_ChunkZ) { + ASSERT(GetWorld()->IsInTickThread()); + auto Chunk = FindChunk(a_ChunkX, a_ChunkZ); if (Chunk == nullptr) { @@ -85,6 +87,7 @@ cChunkPtr cChunkMap::ConstructChunk(int a_ChunkX, int a_ChunkZ) ).first ).second.get(); } + return Chunk; } @@ -94,7 +97,7 @@ cChunkPtr cChunkMap::ConstructChunk(int a_ChunkX, int a_ChunkZ) cChunkPtr cChunkMap::GetChunk(int a_ChunkX, int a_ChunkZ) { - ASSERT(m_CSChunks.IsLockedByCurrentThread()); // m_CSChunks should already be locked by the operation that called us + ASSERT(GetWorld()->IsInTickThread()); auto Chunk = ConstructChunk(a_ChunkX, a_ChunkZ); if (Chunk == nullptr) @@ -116,7 +119,7 @@ cChunkPtr cChunkMap::GetChunk(int a_ChunkX, int a_ChunkZ) cChunkPtr cChunkMap::GetChunkNoGen(int a_ChunkX, int a_ChunkZ) { - ASSERT(m_CSChunks.IsLockedByCurrentThread()); // m_CSChunks should already be locked by the operation that called us + ASSERT(GetWorld()->IsInTickThread()); auto Chunk = ConstructChunk(a_ChunkX, a_ChunkZ); if (Chunk == nullptr) @@ -138,7 +141,7 @@ cChunkPtr cChunkMap::GetChunkNoGen(int a_ChunkX, int a_ChunkZ) cChunkPtr cChunkMap::GetChunkNoLoad(int a_ChunkX, int a_ChunkZ) { - ASSERT(m_CSChunks.IsLockedByCurrentThread()); // m_CSChunks should already be locked by the operation that called us + ASSERT(GetWorld()->IsInTickThread()); return ConstructChunk(a_ChunkX, a_ChunkZ); } @@ -148,8 +151,7 @@ cChunkPtr cChunkMap::GetChunkNoLoad(int a_ChunkX, int a_ChunkZ) bool cChunkMap::LockedGetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta) { - // We already have m_CSChunks locked since this can be called only from within the tick thread - ASSERT(m_CSChunks.IsLockedByCurrentThread()); + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); @@ -170,8 +172,7 @@ bool cChunkMap::LockedGetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTY bool cChunkMap::LockedGetBlockType(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE & a_BlockType) { - // We already have m_CSChunks locked since this can be called only from within the tick thread - ASSERT(m_CSChunks.IsLockedByCurrentThread()); + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); @@ -191,8 +192,7 @@ bool cChunkMap::LockedGetBlockType(int a_BlockX, int a_BlockY, int a_BlockZ, BLO bool cChunkMap::LockedGetBlockMeta(int a_BlockX, int a_BlockY, int a_BlockZ, NIBBLETYPE & a_BlockMeta) { - // We already have m_CSChunks locked since this can be called only from within the tick thread - ASSERT(m_CSChunks.IsLockedByCurrentThread()); + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); @@ -212,7 +212,8 @@ bool cChunkMap::LockedGetBlockMeta(int a_BlockX, int a_BlockY, int a_BlockZ, NIB bool cChunkMap::LockedSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) { - // We already have m_CSChunks locked since this can be called only from within the tick thread + ASSERT(GetWorld()->IsInTickThread()); + int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); cChunkPtr Chunk = GetChunkNoLoad(ChunkX, ChunkZ); @@ -232,6 +233,7 @@ bool cChunkMap::LockedSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTY bool cChunkMap::LockedFastSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) { // We already have m_CSChunks locked since this can be called only from within the tick thread + int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); cChunkPtr Chunk = GetChunkNoLoad(ChunkX, ChunkZ); @@ -250,7 +252,7 @@ bool cChunkMap::LockedFastSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLO cChunk * cChunkMap::FindChunk(int a_ChunkX, int a_ChunkZ) { - ASSERT(m_CSChunks.IsLockedByCurrentThread()); + ASSERT(GetWorld()->IsInTickThread()); auto Chunk = m_Chunks.find({ a_ChunkX, a_ChunkZ }); return (Chunk == m_Chunks.end()) ? nullptr : Chunk->second.get(); @@ -262,7 +264,8 @@ cChunk * cChunkMap::FindChunk(int a_ChunkX, int a_ChunkZ) void cChunkMap::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -278,7 +281,8 @@ void cChunkMap::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity & void cChunkMap::BroadcastBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + int x, z, ChunkX, ChunkZ; x = a_BlockX; z = a_BlockZ; @@ -298,7 +302,8 @@ void cChunkMap::BroadcastBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, c void cChunkMap::BroadcastBlockBreakAnimation(UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + int ChunkX, ChunkZ; cChunkDef::BlockToChunk(a_BlockX, a_BlockZ, ChunkX, ChunkZ); @@ -317,7 +322,8 @@ void cChunkMap::BroadcastBlockBreakAnimation(UInt32 a_EntityID, int a_BlockX, in void cChunkMap::BroadcastBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + int ChunkX, ChunkZ; cChunkDef::BlockToChunk(a_BlockX, a_BlockZ, ChunkX, ChunkZ); cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); @@ -334,7 +340,8 @@ void cChunkMap::BroadcastBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, c void cChunkMap::BroadcastCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -350,7 +357,8 @@ void cChunkMap::BroadcastCollectEntity(const cEntity & a_Entity, const cPlayer & void cChunkMap::BroadcastDestroyEntity(const cEntity & a_Entity, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -366,7 +374,8 @@ void cChunkMap::BroadcastDestroyEntity(const cEntity & a_Entity, const cClientHa void cChunkMap::BroadcastDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -382,7 +391,8 @@ void cChunkMap::BroadcastDetachEntity(const cEntity & a_Entity, const cEntity & void cChunkMap::BroadcastEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -398,7 +408,8 @@ void cChunkMap::BroadcastEntityEffect(const cEntity & a_Entity, int a_EffectID, void cChunkMap::BroadcastEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -414,7 +425,8 @@ void cChunkMap::BroadcastEntityEquipment(const cEntity & a_Entity, short a_SlotN void cChunkMap::BroadcastEntityHeadLook(const cEntity & a_Entity, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -430,7 +442,8 @@ void cChunkMap::BroadcastEntityHeadLook(const cEntity & a_Entity, const cClientH void cChunkMap::BroadcastEntityLook(const cEntity & a_Entity, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -446,7 +459,8 @@ void cChunkMap::BroadcastEntityLook(const cEntity & a_Entity, const cClientHandl void cChunkMap::BroadcastEntityMetadata(const cEntity & a_Entity, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -462,7 +476,8 @@ void cChunkMap::BroadcastEntityMetadata(const cEntity & a_Entity, const cClientH void cChunkMap::BroadcastEntityRelMove(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -478,7 +493,8 @@ void cChunkMap::BroadcastEntityRelMove(const cEntity & a_Entity, char a_RelX, ch void cChunkMap::BroadcastEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -494,7 +510,8 @@ void cChunkMap::BroadcastEntityRelMoveLook(const cEntity & a_Entity, char a_RelX void cChunkMap::BroadcastEntityStatus(const cEntity & a_Entity, char a_Status, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -510,7 +527,8 @@ void cChunkMap::BroadcastEntityStatus(const cEntity & a_Entity, char a_Status, c void cChunkMap::BroadcastEntityVelocity(const cEntity & a_Entity, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -526,7 +544,8 @@ void cChunkMap::BroadcastEntityVelocity(const cEntity & a_Entity, const cClientH void cChunkMap::BroadcastEntityAnimation(const cEntity & a_Entity, char a_Animation, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -542,7 +561,8 @@ void cChunkMap::BroadcastEntityAnimation(const cEntity & a_Entity, char a_Animat void cChunkMap::BroadcastParticleEffect(const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount, cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + int ChunkX, ChunkZ; cChunkDef::BlockToChunk(FloorC(a_SrcX), FloorC(a_SrcZ), ChunkX, ChunkZ); @@ -561,7 +581,8 @@ void cChunkMap::BroadcastParticleEffect(const AString & a_ParticleName, float a_ void cChunkMap::BroadcastRemoveEntityEffect(const cEntity & a_Entity, int a_EffectID, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) @@ -578,7 +599,8 @@ void cChunkMap::BroadcastRemoveEntityEffect(const cEntity & a_Entity, int a_Effe void cChunkMap::BroadcastSoundEffect(const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + int ChunkX, ChunkZ; cChunkDef::BlockToChunk(FloorC(std::floor(a_X)), FloorC(std::floor(a_Z)), ChunkX, ChunkZ); @@ -597,7 +619,8 @@ void cChunkMap::BroadcastSoundEffect(const AString & a_SoundName, double a_X, do void cChunkMap::BroadcastSoundParticleEffect(const EffectID a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + int ChunkX, ChunkZ; cChunkDef::BlockToChunk(a_SrcX, a_SrcZ, ChunkX, ChunkZ); @@ -616,7 +639,8 @@ void cChunkMap::BroadcastSoundParticleEffect(const EffectID a_EffectID, int a_Sr void cChunkMap::BroadcastSpawnEntity(cEntity & a_Entity, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); if (Chunk == nullptr) { @@ -632,7 +656,8 @@ void cChunkMap::BroadcastSpawnEntity(cEntity & a_Entity, const cClientHandle * a void cChunkMap::BroadcastThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + int ChunkX, ChunkZ; cChunkDef::BlockToChunk(a_BlockX, a_BlockZ, ChunkX, ChunkZ); cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); @@ -650,7 +675,8 @@ void cChunkMap::BroadcastThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ, c void cChunkMap::BroadcastUseBed(const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + int ChunkX, ChunkZ; cChunkDef::BlockToChunk(a_BlockX, a_BlockZ, ChunkX, ChunkZ); @@ -669,7 +695,8 @@ void cChunkMap::BroadcastUseBed(const cEntity & a_Entity, int a_BlockX, int a_Bl void cChunkMap::SendBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cClientHandle & a_Client) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + int ChunkX, ChunkZ; cChunkDef::BlockToChunk(a_BlockX, a_BlockZ, ChunkX, ChunkZ); cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); @@ -686,8 +713,9 @@ void cChunkMap::SendBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cClien bool cChunkMap::UseBlockEntity(cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) { + ASSERT(GetWorld()->IsInTickThread()); // a_Player rclked block entity at the coords specified, handle it - cCSLock Lock(m_CSChunks); + int ChunkX, ChunkZ; cChunkDef::BlockToChunk(a_BlockX, a_BlockZ, ChunkX, ChunkZ); cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); @@ -704,7 +732,8 @@ bool cChunkMap::UseBlockEntity(cPlayer * a_Player, int a_BlockX, int a_BlockY, i bool cChunkMap::DoWithChunk(int a_ChunkX, int a_ChunkZ, cChunkCallback & a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ); if (Chunk == nullptr) { @@ -716,6 +745,7 @@ bool cChunkMap::DoWithChunk(int a_ChunkX, int a_ChunkZ, cChunkCallback & a_Callb bool cChunkMap::DoWithChunkAt(Vector3i a_BlockPos, std::function<bool(cChunk &)> a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::BlockToChunk(a_BlockPos.x, a_BlockPos.z, ChunkX, ChunkZ); struct cCallBackWrapper : cChunkCallback @@ -741,7 +771,8 @@ bool cChunkMap::DoWithChunkAt(Vector3i a_BlockPos, std::function<bool(cChunk &)> void cChunkMap::WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + int ChunkX, ChunkZ; cChunkDef::BlockToChunk(a_BlockX, a_BlockZ, ChunkX, ChunkZ); cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); @@ -758,6 +789,7 @@ void cChunkMap::WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ) void cChunkMap::WakeUpSimulatorsInArea(int a_MinBlockX, int a_MaxBlockX, int a_MinBlockY, int a_MaxBlockY, int a_MinBlockZ, int a_MaxBlockZ) { + ASSERT(GetWorld()->IsInTickThread()); // Limit the Y coords: a_MinBlockY = std::max(a_MinBlockY, 0); a_MaxBlockY = std::min(a_MaxBlockY, cChunkDef::Height - 1); @@ -766,7 +798,7 @@ void cChunkMap::WakeUpSimulatorsInArea(int a_MinBlockX, int a_MaxBlockX, int a_M int MinChunkX, MinChunkZ, MaxChunkX, MaxChunkZ; cChunkDef::BlockToChunk(a_MinBlockX, a_MinBlockZ, MinChunkX, MinChunkZ); cChunkDef::BlockToChunk(a_MaxBlockX, a_MaxBlockZ, MaxChunkX, MaxChunkZ); - cCSLock Lock(m_CSChunks); + for (int z = MinChunkZ; z <= MaxChunkZ; z++) { int MinZ = std::max(a_MinBlockZ, z * cChunkDef::Width); @@ -800,7 +832,8 @@ void cChunkMap::WakeUpSimulatorsInArea(int a_MinBlockX, int a_MaxBlockX, int a_M void cChunkMap::MarkChunkDirty(int a_ChunkX, int a_ChunkZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -815,7 +848,8 @@ void cChunkMap::MarkChunkDirty(int a_ChunkX, int a_ChunkZ) void cChunkMap::MarkChunkSaving(int a_ChunkX, int a_ChunkZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -830,7 +864,8 @@ void cChunkMap::MarkChunkSaving(int a_ChunkX, int a_ChunkZ) void cChunkMap::MarkChunkSaved (int a_ChunkX, int a_ChunkZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -848,7 +883,8 @@ void cChunkMap::SetChunkData(cSetChunkData & a_SetChunkData) int ChunkX = a_SetChunkData.GetChunkX(); int ChunkZ = a_SetChunkData.GetChunkZ(); { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoLoad(ChunkX, ChunkZ); if (Chunk == nullptr) { @@ -893,7 +929,8 @@ void cChunkMap::ChunkLighted( const cChunkDef::BlockNibbles & a_SkyLight ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ); if (Chunk == nullptr) { @@ -909,7 +946,8 @@ void cChunkMap::ChunkLighted( bool cChunkMap::GetChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataCallback & a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -925,7 +963,8 @@ bool cChunkMap::GetChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataCallback & a_ bool cChunkMap::GetChunkBlockTypes(int a_ChunkX, int a_ChunkZ, BLOCKTYPE * a_BlockTypes) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -941,7 +980,8 @@ bool cChunkMap::GetChunkBlockTypes(int a_ChunkX, int a_ChunkZ, BLOCKTYPE * a_Blo bool cChunkMap::IsChunkQueued(int a_ChunkX, int a_ChunkZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ); return (Chunk != nullptr) && Chunk->IsQueued(); } @@ -952,7 +992,8 @@ bool cChunkMap::IsChunkQueued(int a_ChunkX, int a_ChunkZ) bool cChunkMap::IsChunkValid(int a_ChunkX, int a_ChunkZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ); return (Chunk != nullptr) && Chunk->IsValid(); } @@ -963,7 +1004,8 @@ bool cChunkMap::IsChunkValid(int a_ChunkX, int a_ChunkZ) bool cChunkMap::HasChunkAnyClients(int a_ChunkX, int a_ChunkZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); return (Chunk != nullptr) && Chunk->HasAnyClients(); } @@ -974,9 +1016,10 @@ bool cChunkMap::HasChunkAnyClients(int a_ChunkX, int a_ChunkZ) int cChunkMap::GetHeight(int a_BlockX, int a_BlockZ) { + ASSERT(GetWorld()->IsInTickThread()); + for (;;) { - cCSLock Lock(m_CSChunks); int ChunkX, ChunkZ, BlockY = 0; cChunkDef::AbsoluteToRelative(a_BlockX, BlockY, a_BlockZ, ChunkX, ChunkZ); cChunkPtr Chunk = GetChunk(ChunkX, ChunkZ); @@ -991,7 +1034,6 @@ int cChunkMap::GetHeight(int a_BlockX, int a_BlockZ) } // The chunk is not valid, wait for it to become valid: - cCSUnlock Unlock(Lock); m_evtChunkValid.Wait(); } // while (true) } @@ -1002,8 +1044,9 @@ int cChunkMap::GetHeight(int a_BlockX, int a_BlockZ) bool cChunkMap::TryGetHeight(int a_BlockX, int a_BlockZ, int & a_Height) { + ASSERT(GetWorld()->IsInTickThread()); // Returns false if chunk not loaded / generated - cCSLock Lock(m_CSChunks); + int ChunkX, ChunkZ, BlockY = 0; cChunkDef::AbsoluteToRelative(a_BlockX, BlockY, a_BlockZ, ChunkX, ChunkZ); cChunkPtr Chunk = GetChunkNoLoad(ChunkX, ChunkZ); @@ -1021,7 +1064,8 @@ bool cChunkMap::TryGetHeight(int a_BlockX, int a_BlockZ, int & a_Height) void cChunkMap::SetBlocks(const sSetBlockVector & a_Blocks) { - cCSLock lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr chunk = nullptr; int lastChunkX = 0x7fffffff; // Bogus coords so that chunk is updated on first pass int lastChunkZ = 0x7fffffff; @@ -1049,6 +1093,7 @@ void cChunkMap::SetBlocks(const sSetBlockVector & a_Blocks) void cChunkMap::CollectPickupsByPlayer(cPlayer & a_Player) { + ASSERT(GetWorld()->IsInTickThread()); int BlockX = static_cast<int>(a_Player.GetPosX()); // Truncating doesn't matter much; we're scanning entire chunks anyway int BlockY = static_cast<int>(a_Player.GetPosY()); int BlockZ = static_cast<int>(a_Player.GetPosZ()); @@ -1060,7 +1105,7 @@ void cChunkMap::CollectPickupsByPlayer(cPlayer & a_Player) // We suppose that each player keeps their chunks in memory, therefore it makes little sense to try to re-load or even generate them. // The only time the chunks are not valid is when the player is downloading the initial world and they should not call this at that moment - cCSLock Lock(m_CSChunks); + GetChunkNoLoad(ChunkX, ChunkZ)->CollectPickupsByPlayer(a_Player); // Check the neighboring chunks as well: @@ -1076,12 +1121,13 @@ void cChunkMap::CollectPickupsByPlayer(cPlayer & a_Player) BLOCKTYPE cChunkMap::GetBlock(int a_BlockX, int a_BlockY, int a_BlockZ) { + ASSERT(GetWorld()->IsInTickThread()); int X = a_BlockX, Y = a_BlockY, Z = a_BlockZ; int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ); // Query the chunk, if loaded: - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunk(ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { @@ -1096,12 +1142,13 @@ BLOCKTYPE cChunkMap::GetBlock(int a_BlockX, int a_BlockY, int a_BlockZ) NIBBLETYPE cChunkMap::GetBlockMeta(int a_BlockX, int a_BlockY, int a_BlockZ) { + ASSERT(GetWorld()->IsInTickThread()); int X = a_BlockX, Y = a_BlockY, Z = a_BlockZ; int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ); // Query the chunk, if loaded: - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunk(ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { @@ -1116,10 +1163,11 @@ NIBBLETYPE cChunkMap::GetBlockMeta(int a_BlockX, int a_BlockY, int a_BlockZ) NIBBLETYPE cChunkMap::GetBlockSkyLight(int a_BlockX, int a_BlockY, int a_BlockZ) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunk( ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { @@ -1134,10 +1182,11 @@ NIBBLETYPE cChunkMap::GetBlockSkyLight(int a_BlockX, int a_BlockY, int a_BlockZ) NIBBLETYPE cChunkMap::GetBlockBlockLight(int a_BlockX, int a_BlockY, int a_BlockZ) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunk( ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { @@ -1152,11 +1201,12 @@ NIBBLETYPE cChunkMap::GetBlockBlockLight(int a_BlockX, int a_BlockY, int a_Block void cChunkMap::SetBlockMeta(int a_BlockX, int a_BlockY, int a_BlockZ, NIBBLETYPE a_BlockMeta, bool a_ShouldMarkDirty, bool a_ShouldInformClients) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); // a_BlockXYZ now contains relative coords! - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunk(ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { @@ -1170,13 +1220,14 @@ void cChunkMap::SetBlockMeta(int a_BlockX, int a_BlockY, int a_BlockZ, NIBBLETYP void cChunkMap::SetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, bool a_SendToClients) { + ASSERT(GetWorld()->IsInTickThread()); cChunkInterface ChunkInterface(this); BlockHandler(GetBlock(a_BlockX, a_BlockY, a_BlockZ))->OnDestroyed(ChunkInterface, *m_World, a_BlockX, a_BlockY, a_BlockZ); int ChunkX, ChunkZ, X = a_BlockX, Y = a_BlockY, Z = a_BlockZ; cChunkDef::AbsoluteToRelative( X, Y, Z, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunk( ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { @@ -1192,10 +1243,11 @@ void cChunkMap::SetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_B bool cChunkMap::GetBlockTypeMeta(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ, X = a_BlockX, Y = a_BlockY, Z = a_BlockZ; cChunkDef::AbsoluteToRelative( X, Y, Z, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunk( ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { @@ -1211,10 +1263,11 @@ bool cChunkMap::GetBlockTypeMeta(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCK bool cChunkMap::GetBlockInfo(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_Meta, NIBBLETYPE & a_SkyLight, NIBBLETYPE & a_BlockLight) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ, X = a_BlockX, Y = a_BlockY, Z = a_BlockZ; cChunkDef::AbsoluteToRelative( X, Y, Z, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunk( ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { @@ -1230,7 +1283,8 @@ bool cChunkMap::GetBlockInfo(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE void cChunkMap::ReplaceBlocks(const sSetBlockVector & a_Blocks, BLOCKTYPE a_FilterBlockType) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + for (sSetBlockVector::const_iterator itr = a_Blocks.begin(); itr != a_Blocks.end(); ++itr) { cChunkPtr Chunk = GetChunk(itr->m_ChunkX, itr->m_ChunkZ); @@ -1251,7 +1305,8 @@ void cChunkMap::ReplaceBlocks(const sSetBlockVector & a_Blocks, BLOCKTYPE a_Filt void cChunkMap::ReplaceTreeBlocks(const sSetBlockVector & a_Blocks) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + for (sSetBlockVector::const_iterator itr = a_Blocks.begin(); itr != a_Blocks.end(); ++itr) { cChunkPtr Chunk = GetChunk(itr->m_ChunkX, itr->m_ChunkZ); @@ -1285,10 +1340,11 @@ void cChunkMap::ReplaceTreeBlocks(const sSetBlockVector & a_Blocks) EMCSBiome cChunkMap::GetBiomeAt (int a_BlockX, int a_BlockZ) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ, X = a_BlockX, Y = 0, Z = a_BlockZ; cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunk(ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { @@ -1306,10 +1362,11 @@ EMCSBiome cChunkMap::GetBiomeAt (int a_BlockX, int a_BlockZ) bool cChunkMap::SetBiomeAt(int a_BlockX, int a_BlockZ, EMCSBiome a_Biome) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ, X = a_BlockX, Y = 0, Z = a_BlockZ; cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunk(ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { @@ -1325,6 +1382,7 @@ bool cChunkMap::SetBiomeAt(int a_BlockX, int a_BlockZ, EMCSBiome a_Biome) bool cChunkMap::SetAreaBiome(int a_MinX, int a_MaxX, int a_MinZ, int a_MaxZ, EMCSBiome a_Biome) { + ASSERT(GetWorld()->IsInTickThread()); // Translate coords to relative: int Y = 0; int MinChunkX, MinChunkZ, MinX = a_MinX, MinZ = a_MinZ; @@ -1334,7 +1392,7 @@ bool cChunkMap::SetAreaBiome(int a_MinX, int a_MaxX, int a_MinZ, int a_MaxZ, EMC // Go through all chunks, set: bool res = true; - cCSLock Lock(m_CSChunks); + for (int x = MinChunkX; x <= MaxChunkX; x++) { int MinRelX = (x == MinChunkX) ? MinX : 0; @@ -1363,8 +1421,9 @@ bool cChunkMap::SetAreaBiome(int a_MinX, int a_MaxX, int a_MinZ, int a_MaxZ, EMC bool cChunkMap::GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure) { + ASSERT(GetWorld()->IsInTickThread()); bool res = true; - cCSLock Lock(m_CSChunks); + for (sSetBlockVector::iterator itr = a_Blocks.begin(); itr != a_Blocks.end(); ++itr) { cChunkPtr Chunk = GetChunk(itr->m_ChunkX, itr->m_ChunkZ); @@ -1389,22 +1448,18 @@ bool cChunkMap::GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure) bool cChunkMap::DigBlock(int a_BlockX, int a_BlockY, int a_BlockZ) { + ASSERT(GetWorld()->IsInTickThread()); int PosX = a_BlockX, PosY = a_BlockY, PosZ = a_BlockZ, ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(PosX, PosY, PosZ, ChunkX, ChunkZ); - + cChunkPtr DestChunk = GetChunk( ChunkX, ChunkZ); + if ((DestChunk == nullptr) || !DestChunk->IsValid()) { - cCSLock Lock(m_CSChunks); - cChunkPtr DestChunk = GetChunk( ChunkX, ChunkZ); - if ((DestChunk == nullptr) || !DestChunk->IsValid()) - { - return false; - } - - DestChunk->SetBlock(PosX, PosY, PosZ, E_BLOCK_AIR, 0); - m_World->GetSimulatorManager()->WakeUp(a_BlockX, a_BlockY, a_BlockZ, DestChunk); + return false; } + DestChunk->SetBlock(PosX, PosY, PosZ, E_BLOCK_AIR, 0); + m_World->GetSimulatorManager()->WakeUp(a_BlockX, a_BlockY, a_BlockZ, DestChunk); return true; } @@ -1414,14 +1469,14 @@ bool cChunkMap::DigBlock(int a_BlockX, int a_BlockY, int a_BlockZ) void cChunkMap::SendBlockTo(int a_X, int a_Y, int a_Z, cPlayer * a_Player) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_X, a_Y, a_Z, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); cChunkPtr Chunk = GetChunk(ChunkX, ChunkZ); - if ((Chunk != nullptr) && (Chunk->IsValid())) + if ((Chunk != nullptr) && Chunk->IsValid()) { - Chunk->SendBlockTo(a_X, a_Y, a_Z, a_Player->GetClientHandle()); + Chunk->SendBlockTo(a_X, a_Y, a_Z, a_Player->GetClientHandlePtr()); } } @@ -1431,7 +1486,8 @@ void cChunkMap::SendBlockTo(int a_X, int a_Y, int a_Z, cPlayer * a_Player) void cChunkMap::CompareChunkClients(int a_ChunkX1, int a_ChunkZ1, int a_ChunkX2, int a_ChunkZ2, cClientDiffCallback & a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk1 = GetChunkNoGen(a_ChunkX1, a_ChunkZ1); if (Chunk1 == nullptr) { @@ -1452,16 +1508,15 @@ void cChunkMap::CompareChunkClients(int a_ChunkX1, int a_ChunkZ1, int a_ChunkX2, void cChunkMap::CompareChunkClients(cChunk * a_Chunk1, cChunk * a_Chunk2, cClientDiffCallback & a_Callback) { - cClientHandleList Clients1(a_Chunk1->GetAllClients()); - cClientHandleList Clients2(a_Chunk2->GetAllClients()); + ASSERT(GetWorld()->IsInTickThread()); // Find "removed" clients: - for (cClientHandleList::iterator itr1 = Clients1.begin(); itr1 != Clients1.end(); ++itr1) + for (const auto & LhsClient : a_Chunk1->GetAllStrongClientPtrs()) { bool Found = false; - for (cClientHandleList::iterator itr2 = Clients2.begin(); itr2 != Clients2.end(); ++itr2) + for (const auto & RhsClient : a_Chunk2->GetAllStrongClientPtrs()) { - if (*itr1 == *itr2) + if (LhsClient == RhsClient) { Found = true; break; @@ -1469,17 +1524,17 @@ void cChunkMap::CompareChunkClients(cChunk * a_Chunk1, cChunk * a_Chunk2, cClien } // for itr2 - Clients2[] if (!Found) { - a_Callback.Removed(*itr1); + a_Callback.Removed(LhsClient.get()); } } // for itr1 - Clients1[] // Find "added" clients: - for (cClientHandleList::iterator itr2 = Clients2.begin(); itr2 != Clients2.end(); ++itr2) + for (const auto & RhsClient : a_Chunk2->GetAllStrongClientPtrs()) { bool Found = false; - for (cClientHandleList::iterator itr1 = Clients1.begin(); itr1 != Clients1.end(); ++itr1) + for (const auto & LhsClient : a_Chunk1->GetAllStrongClientPtrs()) { - if (*itr1 == *itr2) + if (LhsClient == RhsClient) { Found = true; break; @@ -1487,7 +1542,7 @@ void cChunkMap::CompareChunkClients(cChunk * a_Chunk1, cChunk * a_Chunk2, cClien } // for itr1 - Clients1[] if (!Found) { - a_Callback.Added(*itr2); + a_Callback.Added(RhsClient.get()); } } // for itr2 - Clients2[] } @@ -1496,9 +1551,10 @@ void cChunkMap::CompareChunkClients(cChunk * a_Chunk1, cChunk * a_Chunk2, cClien -bool cChunkMap::AddChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client) +bool cChunkMap::AddChunkClient(int a_ChunkX, int a_ChunkZ, const std::shared_ptr<cClientHandle> & a_Client) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunk(a_ChunkX, a_ChunkZ); if (Chunk == nullptr) { @@ -1511,9 +1567,10 @@ bool cChunkMap::AddChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Cli -void cChunkMap::RemoveChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client) +void cChunkMap::RemoveChunkClient(int a_ChunkX, int a_ChunkZ, const std::shared_ptr<cClientHandle> & a_Client) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if (Chunk == nullptr) { @@ -1580,7 +1637,7 @@ void cChunkMap::AddEntityIfNotPresent(cEntity * a_Entity) bool cChunkMap::HasEntity(UInt32 a_UniqueID) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); for (const auto & Chunk : m_Chunks) { if (Chunk.second->IsValid() && Chunk.second->HasEntity(a_UniqueID)) @@ -1595,26 +1652,9 @@ bool cChunkMap::HasEntity(UInt32 a_UniqueID) -void cChunkMap::RemoveEntity(cEntity * a_Entity) -{ - cCSLock Lock(m_CSChunks); - cChunkPtr Chunk = a_Entity->GetParentChunk(); - - // Even if a chunk is not valid, it may still contain entities such as players; make sure to remove them (#1190) - if (Chunk == nullptr) - { - return; - } - Chunk->RemoveEntity(a_Entity); -} - - - - - bool cChunkMap::ForEachEntity(cEntityCallback & a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); for (const auto & Chunk : m_Chunks) { if (Chunk.second->IsValid() && !Chunk.second->ForEachEntity(a_Callback)) @@ -1631,7 +1671,8 @@ bool cChunkMap::ForEachEntity(cEntityCallback & a_Callback) bool cChunkMap::ForEachEntityInChunk(int a_ChunkX, int a_ChunkZ, cEntityCallback & a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -1646,6 +1687,7 @@ bool cChunkMap::ForEachEntityInChunk(int a_ChunkX, int a_ChunkZ, cEntityCallback bool cChunkMap::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); // Calculate the chunk range for the box: int MinChunkX = FloorC(a_Box.GetMinX() / cChunkDef::Width); int MinChunkZ = FloorC(a_Box.GetMinZ() / cChunkDef::Width); @@ -1653,7 +1695,7 @@ bool cChunkMap::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & int MaxChunkZ = FloorC((a_Box.GetMaxZ() + cChunkDef::Width) / cChunkDef::Width); // Iterate over each chunk in the range: - cCSLock Lock(m_CSChunks); + for (int z = MinChunkZ; z <= MaxChunkZ; z++) { for (int x = MinChunkX; x <= MaxChunkX; x++) @@ -1678,6 +1720,7 @@ bool cChunkMap::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & void cChunkMap::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, cVector3iArray & a_BlocksAffected) { + ASSERT(GetWorld()->IsInTickThread()); // Don't explode if outside of Y range (prevents the following test running into unallocated memory): if (!cChunkDef::IsValidHeight(FloorC(a_BlockY))) { @@ -1874,7 +1917,8 @@ void cChunkMap::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_ bool cChunkMap::DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback & a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + bool res = false; for (const auto & Chunk : m_Chunks) { @@ -1892,7 +1936,8 @@ bool cChunkMap::DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback & a_Callback bool cChunkMap::ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEntityCallback & a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -1907,7 +1952,6 @@ bool cChunkMap::ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEnti bool cChunkMap::ForEachBrewingstandInChunk(int a_ChunkX, int a_ChunkZ, cBrewingstandCallback & a_Callback) { - cCSLock Lock(m_CSChunks); cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -1922,7 +1966,8 @@ bool cChunkMap::ForEachBrewingstandInChunk(int a_ChunkX, int a_ChunkZ, cBrewings bool cChunkMap::ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback & a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -1937,7 +1982,8 @@ bool cChunkMap::ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback & bool cChunkMap::ForEachDispenserInChunk(int a_ChunkX, int a_ChunkZ, cDispenserCallback & a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -1952,7 +1998,8 @@ bool cChunkMap::ForEachDispenserInChunk(int a_ChunkX, int a_ChunkZ, cDispenserCa bool cChunkMap::ForEachDropperInChunk(int a_ChunkX, int a_ChunkZ, cDropperCallback & a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -1967,7 +2014,8 @@ bool cChunkMap::ForEachDropperInChunk(int a_ChunkX, int a_ChunkZ, cDropperCallba bool cChunkMap::ForEachDropSpenserInChunk(int a_ChunkX, int a_ChunkZ, cDropSpenserCallback & a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -1982,7 +2030,8 @@ bool cChunkMap::ForEachDropSpenserInChunk(int a_ChunkX, int a_ChunkZ, cDropSpens bool cChunkMap::ForEachFurnaceInChunk(int a_ChunkX, int a_ChunkZ, cFurnaceCallback & a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -1997,10 +2046,11 @@ bool cChunkMap::ForEachFurnaceInChunk(int a_ChunkX, int a_ChunkZ, cFurnaceCallba bool cChunkMap::DoWithBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBlockEntityCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2015,10 +2065,11 @@ bool cChunkMap::DoWithBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ, cB bool cChunkMap::DoWithBeaconAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2036,7 +2087,6 @@ bool cChunkMap::DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, c int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2051,10 +2101,11 @@ bool cChunkMap::DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, c bool cChunkMap::DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2069,10 +2120,11 @@ bool cChunkMap::DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCa bool cChunkMap::DoWithDispenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDispenserCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2087,10 +2139,11 @@ bool cChunkMap::DoWithDispenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDis bool cChunkMap::DoWithDropperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropperCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2105,10 +2158,11 @@ bool cChunkMap::DoWithDropperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropp bool cChunkMap::DoWithDropSpenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2123,10 +2177,11 @@ bool cChunkMap::DoWithDropSpenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cD bool cChunkMap::DoWithFurnaceAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2140,10 +2195,11 @@ bool cChunkMap::DoWithFurnaceAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFurna bool cChunkMap::DoWithNoteBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cNoteBlockCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2157,10 +2213,11 @@ bool cChunkMap::DoWithNoteBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cNot bool cChunkMap::DoWithCommandBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cCommandBlockCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2175,10 +2232,11 @@ bool cChunkMap::DoWithCommandBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, c bool cChunkMap::DoWithMobHeadAt(int a_BlockX, int a_BlockY, int a_BlockZ, cMobHeadCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2193,10 +2251,11 @@ bool cChunkMap::DoWithMobHeadAt(int a_BlockX, int a_BlockY, int a_BlockZ, cMobHe bool cChunkMap::DoWithFlowerPotAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFlowerPotCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2211,10 +2270,11 @@ bool cChunkMap::DoWithFlowerPotAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFlo bool cChunkMap::GetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, AString & a_Line1, AString & a_Line2, AString & a_Line3, AString & a_Line4) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ; cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { @@ -2229,7 +2289,7 @@ bool cChunkMap::GetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, AString & void cChunkMap::TouchChunk(int a_ChunkX, int a_ChunkZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); GetChunk(a_ChunkX, a_ChunkZ); } @@ -2239,7 +2299,8 @@ void cChunkMap::TouchChunk(int a_ChunkX, int a_ChunkZ) void cChunkMap::PrepareChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkCoordCallback> a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ); // If the chunk is not prepared, queue it in the lighting thread, that will do all the needed processing: @@ -2262,7 +2323,8 @@ void cChunkMap::PrepareChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkC bool cChunkMap::GenerateChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ); if (Chunk == nullptr) { @@ -2298,7 +2360,6 @@ bool cChunkMap::GenerateChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * } // The chunk failed to load, generate it: - cCSLock CBLock(m_ChunkMap.m_CSChunks); cChunkPtr CBChunk = m_ChunkMap.GetChunkNoLoad(a_CBChunkX, a_CBChunkZ); if (CBChunk == nullptr) @@ -2338,7 +2399,8 @@ bool cChunkMap::GenerateChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * void cChunkMap::ChunkLoadFailed(int a_ChunkX, int a_ChunkZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ); if (Chunk == nullptr) { @@ -2353,7 +2415,8 @@ void cChunkMap::ChunkLoadFailed(int a_ChunkX, int a_ChunkZ) bool cChunkMap::SetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + int ChunkX, ChunkZ; cChunkDef::BlockToChunk(a_BlockX, a_BlockZ, ChunkX, ChunkZ); cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); @@ -2370,7 +2433,8 @@ bool cChunkMap::SetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, const ASt void cChunkMap::MarkChunkRegenerating(int a_ChunkX, int a_ChunkZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ); if (Chunk == nullptr) { @@ -2386,7 +2450,8 @@ void cChunkMap::MarkChunkRegenerating(int a_ChunkX, int a_ChunkZ) bool cChunkMap::IsChunkLighted(int a_ChunkX, int a_ChunkZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ); if (Chunk == nullptr) { @@ -2402,8 +2467,9 @@ bool cChunkMap::IsChunkLighted(int a_ChunkX, int a_ChunkZ) bool cChunkMap::ForEachChunkInRect(int a_MinChunkX, int a_MaxChunkX, int a_MinChunkZ, int a_MaxChunkZ, cChunkDataCallback & a_Callback) { + ASSERT(GetWorld()->IsInTickThread()); bool Result = true; - cCSLock Lock(m_CSChunks); + for (int z = a_MinChunkZ; z <= a_MaxChunkZ; z++) { for (int x = a_MinChunkX; x <= a_MaxChunkX; x++) @@ -2431,7 +2497,7 @@ bool cChunkMap::ForEachChunkInRect(int a_MinChunkX, int a_MaxChunkX, int a_MinCh bool cChunkMap::ForEachLoadedChunk(std::function<bool(int, int)> a_Callback) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); for (const auto & Chunk : m_Chunks) { if (Chunk.second->IsValid()) @@ -2451,6 +2517,7 @@ bool cChunkMap::ForEachLoadedChunk(std::function<bool(int, int)> a_Callback) bool cChunkMap::WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes) { + ASSERT(GetWorld()->IsInTickThread()); // Convert block coords to chunks coords: int MinChunkX, MaxChunkX; int MinChunkZ, MaxChunkZ; @@ -2465,7 +2532,7 @@ bool cChunkMap::WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBl // Iterate over chunks, write data into each: bool Result = true; - cCSLock Lock(m_CSChunks); + for (int z = MinChunkZ; z <= MaxChunkZ; z++) { for (int x = MinChunkX; x <= MaxChunkX; x++) @@ -2489,9 +2556,10 @@ bool cChunkMap::WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBl void cChunkMap::GetChunkStats(int & a_NumChunksValid, int & a_NumChunksDirty) { + ASSERT(GetWorld()->IsInTickThread()); a_NumChunksValid = 0; a_NumChunksDirty = 0; - cCSLock Lock(m_CSChunks); + for (const auto & Chunk : m_Chunks) { a_NumChunksValid++; @@ -2511,7 +2579,7 @@ bool cChunkMap::GrowMelonPumpkin(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCK int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); cChunkPtr Chunk = GetChunkNoLoad(ChunkX, ChunkZ); if (Chunk != nullptr) { @@ -2526,10 +2594,11 @@ bool cChunkMap::GrowMelonPumpkin(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCK int cChunkMap::GrowSugarcane(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlocksToGrow) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoLoad(ChunkX, ChunkZ); if (Chunk != nullptr) { @@ -2544,10 +2613,11 @@ int cChunkMap::GrowSugarcane(int a_BlockX, int a_BlockY, int a_BlockZ, int a_Num int cChunkMap::GrowCactus(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlocksToGrow) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoLoad(ChunkX, ChunkZ); if (Chunk != nullptr) { @@ -2562,10 +2632,11 @@ int cChunkMap::GrowCactus(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlo bool cChunkMap::GrowTallGrass(int a_BlockX, int a_BlockY, int a_BlockZ) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoLoad(ChunkX, ChunkZ); if (Chunk != nullptr) { @@ -2580,10 +2651,11 @@ bool cChunkMap::GrowTallGrass(int a_BlockX, int a_BlockY, int a_BlockZ) void cChunkMap::SetNextBlockTick(int a_BlockX, int a_BlockY, int a_BlockZ) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoLoad(ChunkX, ChunkZ); if (Chunk != nullptr) { @@ -2596,7 +2668,7 @@ void cChunkMap::SetNextBlockTick(int a_BlockX, int a_BlockY, int a_BlockZ) void cChunkMap::CollectMobCensus(cMobCensus & a_ToFill) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); for (const auto & Chunk : m_Chunks) { // We do count every Mobs in the world. But we are assuming that every chunk not loaded by any client @@ -2616,7 +2688,7 @@ void cChunkMap::CollectMobCensus(cMobCensus & a_ToFill) void cChunkMap::SpawnMobs(cMobSpawner & a_MobSpawner) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); for (const auto & Chunk : m_Chunks) { // We only spawn close to players @@ -2633,7 +2705,7 @@ void cChunkMap::SpawnMobs(cMobSpawner & a_MobSpawner) void cChunkMap::Tick(std::chrono::milliseconds a_Dt) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); for (const auto & Chunk : m_Chunks) { // Only tick chunks that are valid and should be ticked: @@ -2650,7 +2722,8 @@ void cChunkMap::Tick(std::chrono::milliseconds a_Dt) void cChunkMap::TickBlock(int a_BlockX, int a_BlockY, int a_BlockZ) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); cChunkPtr Chunk = GetChunkNoLoad(ChunkX, ChunkZ); @@ -2667,7 +2740,7 @@ void cChunkMap::TickBlock(int a_BlockX, int a_BlockY, int a_BlockZ) void cChunkMap::UnloadUnusedChunks(void) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); for (auto itr = m_Chunks.begin(); itr != m_Chunks.end();) { if ( @@ -2690,7 +2763,7 @@ void cChunkMap::UnloadUnusedChunks(void) void cChunkMap::SaveAllChunks(void) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); for (const auto & Chunk : m_Chunks) { if (Chunk.second->IsValid() && Chunk.second->IsDirty()) @@ -2706,9 +2779,8 @@ void cChunkMap::SaveAllChunks(void) int cChunkMap::GetNumChunks(void) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); return static_cast<int>(m_Chunks.size()); // TODO: change return value to unsigned type - } @@ -2717,6 +2789,7 @@ int cChunkMap::GetNumChunks(void) void cChunkMap::ChunkValidated(void) { + ASSERT(GetWorld()->IsInTickThread()); m_evtChunkValid.Set(); } @@ -2726,11 +2799,12 @@ void cChunkMap::ChunkValidated(void) void cChunkMap::QueueTickBlock(int a_BlockX, int a_BlockY, int a_BlockZ) { + ASSERT(GetWorld()->IsInTickThread()); int ChunkX, ChunkZ; cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); // a_BlockXYZ now contains relative coords! - cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoLoad(ChunkX, ChunkZ); if (Chunk != nullptr) { @@ -2744,7 +2818,8 @@ void cChunkMap::QueueTickBlock(int a_BlockX, int a_BlockY, int a_BlockZ) void cChunkMap::SetChunkAlwaysTicked(int a_ChunkX, int a_ChunkZ, bool a_AlwaysTicked) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); + cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ); if (Chunk != nullptr) { @@ -2762,7 +2837,7 @@ void cChunkMap::FastSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE int ChunkX, ChunkZ, X = a_BlockX, Y = a_BlockY, Z = a_BlockZ; cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ); - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); cChunkPtr Chunk = GetChunk(ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { @@ -2776,11 +2851,11 @@ void cChunkMap::FastSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE void cChunkMap::AddChunkStay(cChunkStay & a_ChunkStay) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); // Add it to the list: ASSERT(std::find(m_ChunkStays.begin(), m_ChunkStays.end(), &a_ChunkStay) == m_ChunkStays.end()); // Has not yet been added - m_ChunkStays.push_back(&a_ChunkStay); + m_ChunkStays.emplace_back(&a_ChunkStay); // Schedule all chunks to be loaded / generated, and mark each as locked: const cChunkCoordsVector & WantedChunks = a_ChunkStay.GetChunks(); @@ -2811,7 +2886,7 @@ void cChunkMap::AddChunkStay(cChunkStay & a_ChunkStay) /** Removes the specified cChunkStay descendant from the internal list of ChunkStays. */ void cChunkMap::DelChunkStay(cChunkStay & a_ChunkStay) { - cCSLock Lock(m_CSChunks); + ASSERT(GetWorld()->IsInTickThread()); // Remove from the list of active chunkstays: bool HasFound = false; diff --git a/src/ChunkMap.h b/src/ChunkMap.h index 328b0f74c..7ca62a41d 100644 --- a/src/ChunkMap.h +++ b/src/ChunkMap.h @@ -67,6 +67,12 @@ public: cChunkMap(cWorld * a_World); ~cChunkMap(); +#ifdef _DEBUG + /** The ID of the thread currently accessing the object. + Used for checking that only one thread accesses the object at a time, via cSingleThreadAccessChecker. */ + mutable std::thread::id m_ThreadID; +#endif + // Broadcast respective packets to all clients of the chunk where the event is taking place // (Please keep these alpha-sorted) void BroadcastAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle); @@ -199,13 +205,10 @@ public: void CompareChunkClients(cChunk * a_Chunk1, cChunk * a_Chunk2, cClientDiffCallback & a_Callback); /** Adds client to a chunk, if not already present; returns true if added, false if present */ - bool AddChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client); + bool AddChunkClient(int a_ChunkX, int a_ChunkZ, const std::shared_ptr<cClientHandle> & a_Client); /** Removes the client from the chunk */ - void RemoveChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client); - - /** Removes the client from all chunks it is present in */ - void RemoveClientFromChunks(cClientHandle * a_Client); + void RemoveChunkClient(int a_ChunkX, int a_ChunkZ, const std::shared_ptr<cClientHandle> & a_Client); /** Adds the entity to its appropriate chunk, takes ownership of the entity pointer */ void AddEntity(cEntity * a_Entity); @@ -395,9 +398,6 @@ public: /** Queues the specified block for ticking (block update) */ void QueueTickBlock(int a_BlockX, int a_BlockY, int a_BlockZ); - /** Returns the CS for locking the chunkmap; only cWorld::cLock may use this function! */ - cCriticalSection & GetCS(void) { return m_CSChunks; } - /** Increments (a_AlwaysTicked == true) or decrements (false) the m_AlwaysTicked counter for the specified chunk. If the m_AlwaysTicked counter is greater than zero, the chunk is ticked in the tick-thread regardless of whether it has any clients or not. @@ -447,7 +447,6 @@ private: typedef std::list<cChunkStay *> cChunkStays; - cCriticalSection m_CSChunks; /** A map of chunk coordinates to chunk pointers Uses a map (as opposed to unordered_map) because sorted maps are apparently faster */ @@ -491,7 +490,7 @@ private: /** Fast-sets a block in any chunk while in the cChunk's Tick() method; returns true if successful, false if chunk not loaded (doesn't queue load) */ bool LockedFastSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); - /** Locates a chunk ptr in the chunkmap; doesn't create it when not found; assumes m_CSChunks is locked. To be called only from cChunkMap. */ + /** Locates a chunk ptr in the chunkmap; doesn't create it when not found. To be called only from cChunkMap. */ cChunk * FindChunk(int a_ChunkX, int a_ChunkZ); /** Adds a new cChunkStay descendant to the internal list of ChunkStays; loads its chunks. diff --git a/src/ChunkSender.cpp b/src/ChunkSender.cpp index ddcd3b534..e27c9f541 100644 --- a/src/ChunkSender.cpp +++ b/src/ChunkSender.cpp @@ -14,44 +14,7 @@ #include "Protocol/ChunkDataSerializer.h" #include "ClientHandle.h" #include "Chunk.h" - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cNotifyChunkSender: - - -/** Callback that can be used to notify chunk sender upon another chunkcoord notification */ -class cNotifyChunkSender : - public cChunkCoordCallback -{ - virtual void Call(int a_ChunkX, int a_ChunkZ, bool a_IsSuccess) override - { - cChunkSender & ChunkSender = m_ChunkSender; - m_World.DoWithChunk( - a_ChunkX, a_ChunkZ, - [&ChunkSender] (cChunk & a_Chunk) -> bool - { - ChunkSender.QueueSendChunkTo(a_Chunk.GetPosX(), a_Chunk.GetPosZ(), cChunkSender::E_CHUNK_PRIORITY_MIDHIGH, a_Chunk.GetAllClients()); - return true; - } - ); - } - - cChunkSender & m_ChunkSender; - - cWorld & m_World; - -public: - cNotifyChunkSender(cChunkSender & a_ChunkSender, cWorld & a_World): - m_ChunkSender(a_ChunkSender), - m_World(a_World) - { - } -}; - +#include "Entities/Player.h" @@ -100,82 +63,38 @@ void cChunkSender::Stop(void) -void cChunkSender::QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, eChunkPriority a_Priority, cClientHandle * a_Client) +void cChunkSender::QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, eChunkPriority a_Priority, const std::weak_ptr<cClientHandle> & a_Client) { - ASSERT(a_Client != nullptr); + ASSERT(!a_Client.expired()); { - cChunkCoords Chunk{a_ChunkX, a_ChunkZ}; + cChunkCoords Chunk{ a_ChunkX, a_ChunkZ }; cCSLock Lock(m_CS); - auto iter = m_ChunkInfo.find(Chunk); - if (iter != m_ChunkInfo.end()) - { - auto & info = iter->second; - if (info.m_Priority > a_Priority) - { - m_SendChunks.push(sChunkQueue{a_Priority, Chunk}); - info.m_Priority = a_Priority; - } - info.m_Clients.insert(a_Client); - } - else - { - m_SendChunks.push(sChunkQueue{a_Priority, Chunk}); - auto info = sSendChunk{Chunk, a_Priority}; - info.m_Clients.insert(a_Client); - m_ChunkInfo.emplace(Chunk, info); - } - } - m_evtQueue.Set(); -} - - - - - -void cChunkSender::QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, eChunkPriority a_Priority, std::list<cClientHandle *> a_Clients) -{ - { - cChunkCoords Chunk{a_ChunkX, a_ChunkZ}; - cCSLock Lock(m_CS); - auto iter = m_ChunkInfo.find(Chunk); - if (iter != m_ChunkInfo.end()) + auto Iterator = std::find_if(m_LoadQueue.begin(), m_LoadQueue.end(), [Chunk](const decltype(m_LoadQueue)::value_type & a_Entry) { return (a_Entry->m_Chunk == Chunk); }); + if (Iterator == m_LoadQueue.end()) { - auto & info = iter->second; - if (info.m_Priority > a_Priority) - { - m_SendChunks.push(sChunkQueue{a_Priority, Chunk}); - info.m_Priority = a_Priority; - } - info.m_Clients.insert(a_Clients.begin(), a_Clients.end()); + auto ChunkStay = new cChunkQueue(a_Priority, Chunk, a_Client, *this); + m_LoadQueue.emplace_back(ChunkStay); + m_World.QueueTask([ChunkStay, this](cWorld & a_World) { ChunkStay->Enable(*m_World.GetChunkMap()); }); } else { - m_SendChunks.push(sChunkQueue{a_Priority, Chunk}); - auto info = sSendChunk{Chunk, a_Priority}; - info.m_Clients.insert(a_Clients.begin(), a_Clients.end()); - m_ChunkInfo.emplace(Chunk, info); + (*Iterator)->m_Priority = std::min(a_Priority, (*Iterator)->m_Priority); + (*Iterator)->m_Clients.emplace_back(a_Client); } } - m_evtQueue.Set(); } -void cChunkSender::RemoveClient(cClientHandle * a_Client) +void cChunkSender::QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, eChunkPriority a_Priority, const std::vector<std::weak_ptr<cClientHandle>> & a_Clients) { + for (const auto Client : a_Clients) { - cCSLock Lock(m_CS); - for (auto && pair : m_ChunkInfo) - { - auto && clients = pair.second.m_Clients; - clients.erase(a_Client); // nop for sets that do not contain a_Client - } + QueueSendChunkTo(a_ChunkX, a_ChunkZ, a_Priority, Client); } - m_evtQueue.Set(); - m_evtRemoved.Wait(); // Wait for all remaining instances of a_Client to be processed (Execute() makes a copy of m_ChunkInfo) } @@ -188,29 +107,29 @@ void cChunkSender::Execute(void) { m_evtQueue.Wait(); + decltype(m_SendChunks) QueuedChunks; { cCSLock Lock(m_CS); - while (!m_SendChunks.empty()) + std::swap(m_SendChunks, QueuedChunks); + } + + std::sort(QueuedChunks.begin(), QueuedChunks.end(), [](const decltype(QueuedChunks)::value_type & a_Lhs, const decltype(QueuedChunks)::value_type & a_Rhs) { - // Take one from the queue: - auto Chunk = m_SendChunks.top().m_Chunk; - m_SendChunks.pop(); - auto itr = m_ChunkInfo.find(Chunk); - if (itr == m_ChunkInfo.end()) - { - continue; - } - - std::unordered_set<cClientHandle *> clients; - std::swap(itr->second.m_Clients, clients); - m_ChunkInfo.erase(itr); - - cCSUnlock Unlock(Lock); - SendChunk(Chunk.m_ChunkX, Chunk.m_ChunkZ, clients); + /* The Standard Priority Queue sorts from biggest to smallest + return true here means you are smaller than the other object, and you get pushed down. + + The priorities go from HIGH (0) to LOW (3), so a smaller priority should mean further up the list + therefore, return true (affirm we're "smaller", and get pushed down) only if our priority is bigger than theirs (they're more urgent) + */ + return a_Lhs->m_Priority < a_Rhs->m_Priority; } - } + ); - m_evtRemoved.SetAll(); // Notify all waiting threads that all clients are processed and thus safe to destroy + for (const auto & Entry : QueuedChunks) + { + SendChunk(*Entry); + m_World.QueueTask([Entry](cWorld & a_World) { Entry->Disable(); }); + } } // while (!m_ShouldTerminate) } @@ -218,86 +137,28 @@ void cChunkSender::Execute(void) -void cChunkSender::SendChunk(int a_ChunkX, int a_ChunkZ, std::unordered_set<cClientHandle *> a_Clients) +void cChunkSender::SendChunk(const cChunkQueue & a_Item) { - // Ask the client if it still wants the chunk: - for (auto itr = a_Clients.begin(); itr != a_Clients.end();) + cChunkDataSerializer Data(a_Item.m_BlockTypes, a_Item.m_BlockMetas, a_Item.m_BlockLight, a_Item.m_BlockSkyLight, a_Item.m_BiomeMap, m_World.GetDimension()); + for (const auto Client : a_Item.m_Clients) { - if (!(*itr)->WantsSendChunk(a_ChunkX, a_ChunkZ)) - { - itr = a_Clients.erase(itr); - } - else + // Atomically acquired shared_ptr; thread safe + auto ClientPointer = Client.lock(); + if (ClientPointer == nullptr) { - itr++; + continue; } - } - - // If the chunk has no clients, no need to packetize it: - if (!m_World.HasChunkAnyClients(a_ChunkX, a_ChunkZ)) - { - return; - } - // If the chunk is not valid, do nothing - whoever needs it has queued it for loading / generating - if (!m_World.IsChunkValid(a_ChunkX, a_ChunkZ)) - { - return; - } - - // If the chunk is not lighted, queue it for relighting and get notified when it's ready: - if (!m_World.IsChunkLighted(a_ChunkX, a_ChunkZ)) - { - m_World.QueueLightChunk(a_ChunkX, a_ChunkZ, cpp14::make_unique<cNotifyChunkSender>(*this, m_World)); - return; - } - - // Query and prepare chunk data: - if (!m_World.GetChunkData(a_ChunkX, a_ChunkZ, *this)) - { - return; - } - cChunkDataSerializer Data(m_BlockTypes, m_BlockMetas, m_BlockLight, m_BlockSkyLight, m_BiomeMap, m_World.GetDimension()); - - for (const auto client : a_Clients) - { // Send: - client->SendChunkData(a_ChunkX, a_ChunkZ, Data); - - // Send block-entity packets: - for (const auto & Pos : m_BlockEntities) - { - m_World.SendBlockEntity(Pos.x, Pos.y, Pos.z, *client); - } // for itr - m_Packets[] - + ClientPointer->SendChunkData(m_World, a_Item.m_Chunk, Data); } - m_BlockEntities.clear(); - - // TODO: Send entity spawn packets } -void cChunkSender::BlockEntity(cBlockEntity * a_Entity) -{ - m_BlockEntities.push_back(a_Entity->GetPos()); -} - - - - -void cChunkSender::Entity(cEntity *) -{ - // Nothing needed yet, perhaps in the future when we save entities into chunks we'd like to send them upon load, too ;) -} - - - - - -void cChunkSender::BiomeData(const cChunkDef::BiomeMap * a_BiomeMap) +void cChunkSender::cChunkQueue::BiomeData(const cChunkDef::BiomeMap * a_BiomeMap) { for (size_t i = 0; i < ARRAYCOUNT(m_BiomeMap); i++) { @@ -317,3 +178,17 @@ void cChunkSender::BiomeData(const cChunkDef::BiomeMap * a_BiomeMap) + +bool cChunkSender::cChunkQueue::OnAllChunksAvailable() +{ + VERIFY(m_ChunkSender.m_World.GetChunkData(m_Chunk.m_ChunkX, m_Chunk.m_ChunkZ, *this)); + + { + cCSLock Lock(m_ChunkSender.m_CS); + m_ChunkSender.m_LoadQueue.erase(std::remove(m_ChunkSender.m_LoadQueue.begin(), m_ChunkSender.m_LoadQueue.end(), this), m_ChunkSender.m_LoadQueue.end()); + m_ChunkSender.m_SendChunks.push_back(this); + } + + m_ChunkSender.m_evtQueue.Set(); + return false; +} diff --git a/src/ChunkSender.h b/src/ChunkSender.h index a7b1e104b..9c391a6fc 100644 --- a/src/ChunkSender.h +++ b/src/ChunkSender.h @@ -28,9 +28,7 @@ Note that it may be called by world's BroadcastToChunk() if the client is still #include "OSSupport/IsThread.h" #include "ChunkDef.h" #include "ChunkDataCallback.h" - -#include <unordered_set> -#include <unordered_map> +#include "ChunkStay.h" @@ -51,8 +49,7 @@ class cChunkSender; class cChunkSender: - public cIsThread, - public cChunkDataSeparateCollector + public cIsThread { typedef cIsThread super; public: @@ -72,70 +69,68 @@ public: void Stop(void); - /** Queues a chunk to be sent to a specific client */ - void QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, eChunkPriority a_Priority, cClientHandle * a_Client); - void QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, eChunkPriority a_Priority, std::list<cClientHandle *> a_Client); - - /** Removes the a_Client from all waiting chunk send operations */ - void RemoveClient(cClientHandle * a_Client); + /** Queues a chunk to be sent to a specific client + A chunkstay ensures that the chunk is valid before sending */ + void QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, eChunkPriority a_Priority, const std::weak_ptr<cClientHandle> & a_Client); + void QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, eChunkPriority a_Priority, const std::vector<std::weak_ptr<cClientHandle>> & a_Clients); protected: - struct sChunkQueue + class cChunkQueue : + public cChunkStay, + public cChunkDataSeparateCollector { - eChunkPriority m_Priority; - cChunkCoords m_Chunk; + public: - bool operator <(const sChunkQueue & a_Other) const + cChunkQueue(eChunkPriority a_Priority, cChunkCoords a_ChunkCoordinate, const std::weak_ptr<cClientHandle> & a_Client, cChunkSender & a_ChunkSender) : + m_Priority(a_Priority), + m_Chunk(a_ChunkCoordinate), + m_Clients{ a_Client }, + m_ChunkSender(a_ChunkSender) { - /* The Standard Priority Queue sorts from biggest to smallest - return true here means you are smaller than the other object, and you get pushed down. - - The priorities go from HIGH (0) to LOW (2), so a smaller priority should mean further up the list - therefore, return true (affirm we're "smaller", and get pushed down) only if our priority is bigger than theirs (they're more urgent) - */ - return this->m_Priority > a_Other.m_Priority; + Add(a_ChunkCoordinate.m_ChunkX, a_ChunkCoordinate.m_ChunkZ); } - }; - /** Used for sending chunks to specific clients */ - struct sSendChunk - { - cChunkCoords m_Chunk; - std::unordered_set<cClientHandle *> m_Clients; eChunkPriority m_Priority; - sSendChunk(cChunkCoords a_Chunk, eChunkPriority a_Priority) : - m_Chunk(a_Chunk), - m_Priority(a_Priority) + cChunkCoords m_Chunk; + std::vector<std::weak_ptr<cClientHandle>> m_Clients; + + // Data about the chunk that is being sent: + // NOTE that m_BlockData[] is inherited from the cChunkDataCollector + unsigned char m_BiomeMap[cChunkDef::Width * cChunkDef::Width]; + + private: + cChunkSender & m_ChunkSender; + + // cChunkDataCollector overrides: + // (Note that they are called while the ChunkMap's CS is locked - don't do heavy calculations here!) + virtual void BiomeData(const cChunkDef::BiomeMap * a_BiomeMap) override; + + // cChunkStay overrides + virtual void OnChunkAvailable(int a_ChunkX, int a_ChunkZ) override { + UNUSED(a_ChunkX); + UNUSED(a_ChunkZ); + } + virtual bool OnAllChunksAvailable(void) override; + virtual void OnDisabled(void) override + { + delete this; } }; cWorld & m_World; cCriticalSection m_CS; - std::priority_queue<sChunkQueue> m_SendChunks; - std::unordered_map<cChunkCoords, sSendChunk, cChunkCoordsHash> m_ChunkInfo; + std::vector<cChunkQueue *> m_LoadQueue; + std::vector<cChunkQueue *> m_SendChunks; cEvent m_evtQueue; // Set when anything is added to m_ChunksReady - cEvent m_evtRemoved; // Set when removed clients are safe to be deleted - - // Data about the chunk that is being sent: - // NOTE that m_BlockData[] is inherited from the cChunkDataCollector - unsigned char m_BiomeMap[cChunkDef::Width * cChunkDef::Width]; - std::vector<Vector3i> m_BlockEntities; // Coords of the block entities to send - // TODO: sEntityIDs m_Entities; // Entity-IDs of the entities to send // cIsThread override: virtual void Execute(void) override; - // cChunkDataCollector overrides: - // (Note that they are called while the ChunkMap's CS is locked - don't do heavy calculations here!) - virtual void BiomeData (const cChunkDef::BiomeMap * a_BiomeMap) override; - virtual void Entity (cEntity * a_Entity) override; - virtual void BlockEntity (cBlockEntity * a_Entity) override; - /** Sends the specified chunk to all the specified clients */ - void SendChunk(int a_ChunkX, int a_ChunkZ, std::unordered_set<cClientHandle *> a_Clients); + void SendChunk(const cChunkQueue & a_Item); } ; diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 7dbf5a0a4..f49304ebe 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -1,5 +1,6 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules +#include "Chunk.h" #include "ClientHandle.h" #include "Server.h" #include "World.h" @@ -65,11 +66,9 @@ cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) : m_CurrentViewDistance(a_ViewDistance), m_RequestedViewDistance(a_ViewDistance), m_IPString(a_IPString), - m_Player(nullptr), - m_CachedSentChunk(0, 0), - m_HasSentDC(false), - m_LastStreamedChunkX(0x7fffffff), // bogus chunk coords to force streaming upon login - m_LastStreamedChunkZ(0x7fffffff), + m_ShouldDestroyPlayer(true), + m_Protocol(this), + m_Player(nullptr), // In order to catch when client leaves before authentication m_TicksSinceLastPacket(0), m_Ping(1000), m_PingID(1), @@ -82,18 +81,15 @@ cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) : m_LastDigBlockX(0), m_LastDigBlockY(256), // Invalid Y, so that the coords don't get picked up m_LastDigBlockZ(0), - m_State(csConnected), - m_ShouldCheckDownloaded(false), + m_State(eState::csConnected), + m_ShouldRefreshSentChunks(true), m_NumExplosionsThisTick(0), m_NumBlockChangeInteractionsThisTick(0), m_UniqueID(0), - m_HasSentPlayerChunk(false), m_Locale("en_GB"), m_LastPlacedSign(0, -1, 0), m_ProtocolVersion(0) { - m_Protocol = new cProtocolRecognizer(this); - s_ClientCount++; // Not protected by CS because clients are always constructed from the same thread m_UniqueID = s_ClientCount; m_PingStartTime = std::chrono::steady_clock::now(); @@ -107,43 +103,31 @@ cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) : cClientHandle::~cClientHandle() { - ASSERT(m_State == csDestroyed); // Has Destroy() been called? + ASSERT(m_State == eState::csDestroyed); // Has Destroy() been called? - LOGD("Deleting client \"%s\" at %p", GetUsername().c_str(), static_cast<void *>(this)); - - { - cCSLock Lock(m_CSChunkLists); - m_LoadedChunks.clear(); - m_ChunksToSend.clear(); - } - - if (m_Player != nullptr) + if ( + m_ShouldDestroyPlayer && // If server is shutting down, m_Player is considered invalid. It will be cleaned up by cWorld + (m_Player != nullptr) + ) { cWorld * World = m_Player->GetWorld(); - if (World != nullptr) - { - RemoveFromAllChunks(); - m_Player->GetWorld()->RemoveClientFromChunkSender(this); - if (!m_Username.empty()) + + // 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) { - // Send the Offline PlayerList packet: - World->BroadcastPlayerListRemovePlayer(*m_Player, this); + a_World.RemovePlayer(m_Player); + a_World.BroadcastPlayerListRemovePlayer(*m_Player); + m_Player->Destroy(); } - m_Player->DestroyNoScheduling(true); - } - delete m_Player; - m_Player = nullptr; - } - - if (!m_HasSentDC) - { - SendDisconnect("Server shut down? Kthnxbai"); + ); } - delete m_Protocol; - m_Protocol = nullptr; - - LOGD("ClientHandle at %p deleted", static_cast<void *>(this)); + LOGD("Deletied client \"%s\" at %p", GetUsername().c_str(), static_cast<void *>(this)); } @@ -152,35 +136,47 @@ cClientHandle::~cClientHandle() void cClientHandle::Destroy(void) { + ASSERT(cRoot::Get()->GetServer()->IsInTickThread()); + if (!SetState(eState::csDestroying)) { - cCSLock Lock(m_CSOutgoingData); - m_Link.reset(); + // Already destroyed. + return; } + { - cCSLock Lock(m_CSDestroyingState); - if (m_State >= csDestroying) - { - // Already called - return; - } - m_State = csDestroying; + cCSLock Lock(m_CSLink); + m_Link.reset(); } + RemoveFromWorld(); + LOGD("%s: client %p, \"%s\"", __FUNCTION__, static_cast<void *>(this), m_Username.c_str()); + SetState(eState::csDestroyed); +} - if (m_Player != nullptr) + + + + +bool cClientHandle::SetState(eState a_State) +{ + // Obtain state at a point in time to compare to a_State + auto ExpectedState = m_State.load(); + + while ( + // Ensure that an exchange will be performed validly + (ExpectedState < a_State) && + + // Ensure that the current state matches what we have just verified + // If so, set the desired state + // Else, update ExpectedState to be the current value in preparation for another check and loop + !m_State.compare_exchange_weak(ExpectedState, a_State) + ) { - cWorld * World = m_Player->GetWorld(); - if (World != nullptr) - { - m_Player->StopEveryoneFromTargetingMe(); - m_Player->SetIsTicking(false); - World->RemovePlayer(m_Player, true); - } - m_Player->RemoveClientHandle(); + continue; } - m_State = csDestroyed; + return (ExpectedState < a_State); } @@ -304,7 +300,7 @@ bool cClientHandle::IsUUIDOnline(const AString & a_UUID) void cClientHandle::Kick(const AString & a_Reason) { - if (m_State >= csAuthenticating) // Don't log pings + if (IsLoggedIn()) // Don't log pings { LOGINFO("Kicking player %s for \"%s\"", m_Username.c_str(), StripColorCodes(a_Reason).c_str()); } @@ -317,13 +313,14 @@ void cClientHandle::Kick(const AString & a_Reason) void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID, const Json::Value & a_Properties) { - if (m_State != csAuthenticating) + ASSERT(cRoot::Get()->GetServer()->IsInTickThread()); + + if (m_State != eState::csAuthenticating) { + // TODO: is this necessary? return; } - ASSERT(m_Player == nullptr); - m_Username = a_Name; // Only assign UUID and properties if not already pre-assigned (BungeeCord sends those in the Handshake packet): @@ -337,108 +334,116 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID, } // Send login success (if the protocol supports it): - m_Protocol->SendLoginSuccess(); + m_Protocol.SendLoginSuccess(); + + // Obtain a strong reference to ourselves + auto Client = shared_from_this(); // Spawn player (only serversided, so data is loaded) - m_Player = new cPlayer(m_Self, GetUsername()); - InvalidateCachedSentChunk(); - m_Self.reset(); + auto Player = cpp14::make_unique<cPlayer>(Client, m_Username); + m_Player = Player.get(); cWorld * World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName()); if (World == nullptr) { World = cRoot::Get()->GetDefaultWorld(); - m_Player->SetPosition(World->GetSpawnX(), World->GetSpawnY(), World->GetSpawnZ()); } if (m_Player->GetGameMode() == eGameMode_NotSet) { m_Player->LoginSetGameMode(World->GetGameMode()); } + m_Player->SetIP(m_IPString); - m_Player->SetIP (m_IPString); + 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); - if (!cRoot::Get()->GetPluginManager()->CallHookPlayerJoined(*m_Player)) - { - cRoot::Get()->BroadcastChatJoin(Printf("%s has joined the game", GetUsername().c_str())); - LOGINFO("Player %s has joined the game", m_Username.c_str()); - } + if (!cRoot::Get()->GetPluginManager()->CallHookPlayerJoined(*Player)) + { + cRoot::Get()->BroadcastChatJoin(Printf("%s has joined the game", Client->GetUsername().c_str())); + LOGINFO("Player %s has joined the game", Client->GetUsername().c_str()); + } - m_ConfirmPosition = m_Player->GetPosition(); + // Return a server login packet + Client->GetProtocol().SendLogin(*Player, *World); - // Return a server login packet - m_Protocol->SendLogin(*m_Player, *World); + // Send Weather if raining: + if ((World->GetWeather() == 1) || (World->GetWeather() == 2)) + { + Client->GetProtocol().SendWeather(World->GetWeather()); + } - // Send Weather if raining: - if ((World->GetWeather() == 1) || (World->GetWeather() == 2)) - { - m_Protocol->SendWeather(World->GetWeather()); - } + // Send time: + Client->GetProtocol().SendTimeUpdate(World->GetWorldAge(), World->GetTimeOfDay(), World->IsDaylightCycleEnabled()); + + // Send contents of the inventory window + Client->GetProtocol().SendWholeInventory(*Player->GetWindow()); - // Send time: - m_Protocol->SendTimeUpdate(World->GetWorldAge(), World->GetTimeOfDay(), World->IsDaylightCycleEnabled()); + // Send health + Client->GetProtocol().SendHealth(); - // Send contents of the inventory window - m_Protocol->SendWholeInventory(*m_Player->GetWindow()); + // Send experience + Client->GetProtocol().SendExperience(); - // Send health - m_Player->SendHealth(); + // Send player list items + cRoot::Get()->BroadcastPlayerListsAddPlayer(*Player); + cRoot::Get()->SendPlayerLists(Player); - // Send experience - m_Player->SendExperience(); + // Note: cEntity::Initialize takes ownership of the player object + Player->Initialize(std::move(PlayerPtr.value), *World); - // Send player list items - SendPlayerListAddPlayer(*m_Player); - cRoot::Get()->BroadcastPlayerListsAddPlayer(*m_Player); - cRoot::Get()->SendPlayerLists(m_Player); + World->AddPlayer(Player); + Client->SetState(cClientHandle::eState::csAuthenticated); - m_Player->SetWorld(World); - m_State = csAuthenticated; + // Query player team + Player->UpdateTeam(); - // Query player team - m_Player->UpdateTeam(); + // Send scoreboard data + World->GetScoreBoard().SendTo(*Client); - // Send scoreboard data - World->GetScoreBoard().SendTo(*this); + // Send statistics + Client->GetProtocol().SendStatistics(Player->GetStatManager()); - // Send statistics - SendStatistics(m_Player->GetStatManager()); + cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*Player); + } + ); // Delay the first ping until the client "settles down" // This should fix #889, "BadCast exception, cannot convert bit to fm" error in client m_PingStartTime = std::chrono::steady_clock::now() + std::chrono::seconds(3); // Send the first KeepAlive packet in 3 seconds - - cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*m_Player); } -bool cClientHandle::StreamNextChunk(void) +void cClientHandle::StreamAllChunks(void) { - if ((m_State < csAuthenticated) || (m_State >= csDestroying)) + ASSERT((m_State >= eState::csAuthenticated) || (m_State < eState::csDestroying)); + ASSERT(m_Player != nullptr); + + if (m_Player->IsChangingWorlds()) { - return true; + // Make a best-effort attempt at bailing. + return; } - ASSERT(m_Player != nullptr); int ChunkPosX = m_Player->GetChunkX(); int ChunkPosZ = m_Player->GetChunkZ(); - if ((m_LastStreamedChunkX == ChunkPosX) && (m_LastStreamedChunkZ == ChunkPosZ)) - { - // All chunks are already loaded. Abort loading. - return true; - } + + std::vector<std::pair<cChunkCoords, cChunkSender::eChunkPriority>> ChunksToSend = { + { cChunkCoords(ChunkPosX, ChunkPosZ), cChunkSender::E_CHUNK_PRIORITY_HIGH } + }; // Get the look vector and normalize it. Vector3d Position = m_Player->GetEyePosition(); Vector3d LookVector = m_Player->GetLookVector(); LookVector.Normalize(); - // Lock the list - cCSLock Lock(m_CSChunkLists); - // High priority: Load the chunks that are in the view-direction of the player (with a radius of 3) for (int Range = 0; Range < m_CurrentViewDistance; Range++) { @@ -462,19 +467,13 @@ bool cClientHandle::StreamNextChunk(void) continue; } - // If the chunk already loading / loaded -> skip - if ( - (m_ChunksToSend.find(Coords) != m_ChunksToSend.end()) || - (m_LoadedChunks.find(Coords) != m_LoadedChunks.end()) - ) - { - continue; - } - - // Unloaded chunk found -> Send it to the client. - Lock.Unlock(); - StreamChunk(ChunkX, ChunkZ, ((Range <= 2) ? cChunkSender::E_CHUNK_PRIORITY_HIGH : cChunkSender::E_CHUNK_PRIORITY_MEDIUM)); - return false; + // Send the chunk; StreamChunk queues a task which will abort if it has been called for the chunk already (we are part of the chunk's clientlist) + ChunksToSend.emplace_back( + std::make_pair( + cChunkCoords(ChunkX, ChunkZ), + (Range <= 2) ? cChunkSender::E_CHUNK_PRIORITY_HIGH : cChunkSender::E_CHUNK_PRIORITY_MEDIUM + ) + ); } } } @@ -483,144 +482,72 @@ bool cClientHandle::StreamNextChunk(void) for (int d = 0; d <= m_CurrentViewDistance; ++d) // cycle through (square) distance, from nearest to furthest { // For each distance add chunks in a hollow square centered around current position: - cChunkCoordsList CurcleChunks; for (int i = -d; i <= d; ++i) { - CurcleChunks.push_back(cChunkCoords(ChunkPosX + d, ChunkPosZ + i)); - CurcleChunks.push_back(cChunkCoords(ChunkPosX - d, ChunkPosZ + i)); + ChunksToSend.emplace_back(std::make_pair(cChunkCoords(ChunkPosX + d, ChunkPosZ + i), cChunkSender::E_CHUNK_PRIORITY_LOW)); + ChunksToSend.emplace_back(std::make_pair(cChunkCoords(ChunkPosX - d, ChunkPosZ + i), cChunkSender::E_CHUNK_PRIORITY_LOW)); } for (int i = -d + 1; i < d; ++i) { - CurcleChunks.push_back(cChunkCoords(ChunkPosX + i, ChunkPosZ + d)); - CurcleChunks.push_back(cChunkCoords(ChunkPosX + i, ChunkPosZ - d)); - } - - // For each the CurcleChunks list and send the first unloaded chunk: - for (cChunkCoordsList::iterator itr = CurcleChunks.begin(), end = CurcleChunks.end(); itr != end; ++itr) - { - cChunkCoords Coords = *itr; - - // If the chunk already loading / loaded -> skip - if ( - (m_ChunksToSend.find(Coords) != m_ChunksToSend.end()) || - (m_LoadedChunks.find(Coords) != m_LoadedChunks.end()) - ) - { - continue; - } - - // Unloaded chunk found -> Send it to the client. - Lock.Unlock(); - StreamChunk(Coords.m_ChunkX, Coords.m_ChunkZ, cChunkSender::E_CHUNK_PRIORITY_LOW); - return false; + ChunksToSend.emplace_back(std::make_pair(cChunkCoords(ChunkPosZ + i, ChunkPosZ + d), cChunkSender::E_CHUNK_PRIORITY_LOW)); + ChunksToSend.emplace_back(std::make_pair(cChunkCoords(ChunkPosZ + i, ChunkPosZ - d), cChunkSender::E_CHUNK_PRIORITY_LOW)); } } - // All chunks are loaded -> Sets the last loaded chunk coordinates to current coordinates - m_LastStreamedChunkX = ChunkPosX; - m_LastStreamedChunkZ = ChunkPosZ; - return true; -} - - - - - -void cClientHandle::UnloadOutOfRangeChunks(void) -{ - int ChunkPosX = FAST_FLOOR_DIV(static_cast<int>(m_Player->GetPosX()), cChunkDef::Width); - int ChunkPosZ = FAST_FLOOR_DIV(static_cast<int>(m_Player->GetPosZ()), cChunkDef::Width); - - cChunkCoordsList ChunksToRemove; - { - cCSLock Lock(m_CSChunkLists); - for (auto itr = m_LoadedChunks.begin(); itr != m_LoadedChunks.end();) + m_Player->GetWorld()->QueueTask( + [ChunksToSend = std::move(ChunksToSend), ClientHandle = shared_from_this()](cWorld & a_World) { - int DiffX = Diff((*itr).m_ChunkX, ChunkPosX); - int DiffZ = Diff((*itr).m_ChunkZ, ChunkPosZ); - if ((DiffX > m_CurrentViewDistance) || (DiffZ > m_CurrentViewDistance)) - { - ChunksToRemove.push_back(*itr); - itr = m_LoadedChunks.erase(itr); - } - else + for (const auto & Chunk : ChunksToSend) { - ++itr; - } - } + if (a_World.AddChunkClient(Chunk.first.m_ChunkX, Chunk.first.m_ChunkZ, ClientHandle)) + { + a_World.GetChunkSender().QueueSendChunkTo(Chunk.first.m_ChunkX, Chunk.first.m_ChunkZ, Chunk.second, ClientHandle); - for (auto itr = m_ChunksToSend.begin(); itr != m_ChunksToSend.end();) - { - int DiffX = Diff((*itr).m_ChunkX, ChunkPosX); - int DiffZ = Diff((*itr).m_ChunkZ, ChunkPosZ); - if ((DiffX > m_CurrentViewDistance) || (DiffZ > m_CurrentViewDistance)) - { - itr = m_ChunksToSend.erase(itr); - } - else - { - ++itr; + // Clarification: std::partition, which cWorld::TickQueuedTasks uses, doesn't guarantee ordering + // However, we won't end up with a situation where a RemoveChunkClient ends up before an AddChunkClient for a chunk + // because UnloadChunk / RemoveChunkClient is based on our m_LoadedChunks list, which is only updated when this task to add a client has finished. + } } } - } - - for (cChunkCoordsList::iterator itr = ChunksToRemove.begin(); itr != ChunksToRemove.end(); ++itr) - { - m_Player->GetWorld()->RemoveChunkClient(itr->m_ChunkX, itr->m_ChunkZ, this); - SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ); - } + ); } - -void cClientHandle::StreamChunk(int a_ChunkX, int a_ChunkZ, cChunkSender::eChunkPriority a_Priority) +void cClientHandle::UnloadOutOfRangeChunks(void) { - if (m_State >= csDestroying) + if (m_LoadedChunks.empty()) { - // Don't stream chunks to clients that are being destroyed return; } - cWorld * World = m_Player->GetWorld(); - ASSERT(World != nullptr); + int ChunkPosX = FAST_FLOOR_DIV(static_cast<int>(m_Player->GetPosX()), cChunkDef::Width); + int ChunkPosZ = FAST_FLOOR_DIV(static_cast<int>(m_Player->GetPosZ()), cChunkDef::Width); - if (World->AddChunkClient(a_ChunkX, a_ChunkZ, this)) + for (auto EraseItr = m_LoadedChunks.begin(); EraseItr != m_LoadedChunks.end();) { + int DiffX = Diff(EraseItr->first.m_ChunkX, ChunkPosX); + int DiffZ = Diff(EraseItr->first.m_ChunkZ, ChunkPosZ); + if ( + (DiffX <= m_CurrentViewDistance) && + (DiffZ <= m_CurrentViewDistance) && + (m_Player->GetWorld() == &EraseItr->second.get()) + ) { - cCSLock Lock(m_CSChunkLists); - m_LoadedChunks.emplace(a_ChunkX, a_ChunkZ); - m_ChunksToSend.emplace(a_ChunkX, a_ChunkZ); + ++EraseItr; + continue; } - World->SendChunkTo(a_ChunkX, a_ChunkZ, a_Priority, this); - } -} - + EraseItr->second.get().QueueTask( + [ChunkCoordinates = EraseItr->first, ClientHandle = shared_from_this()](cWorld & a_World) + { + a_World.RemoveChunkClient(ChunkCoordinates.m_ChunkX, ChunkCoordinates.m_ChunkZ, ClientHandle); + } + ); + m_Protocol.SendUnloadChunk(EraseItr->first.m_ChunkX, EraseItr->first.m_ChunkZ); - - -// Removes the client from all chunks. Used when switching worlds or destroying the player -void cClientHandle::RemoveFromAllChunks() -{ - cWorld * World = m_Player->GetWorld(); - if (World != nullptr) - { - World->RemoveClientFromChunks(this); - } - - { - // Reset all chunk lists: - cCSLock Lock(m_CSChunkLists); - m_LoadedChunks.clear(); - m_ChunksToSend.clear(); - m_SentChunks.clear(); - - // Also reset the LastStreamedChunk coords to bogus coords, - // so that all chunks are streamed in subsequent StreamChunks() call (FS #407) - m_LastStreamedChunkX = 0x7fffffff; - m_LastStreamedChunkZ = 0x7fffffff; + EraseItr = m_LoadedChunks.erase(EraseItr); } } @@ -638,26 +565,6 @@ void cClientHandle::HandleNPCTrade(int a_SlotNum) -void cClientHandle::HandlePing(void) -{ - // Somebody tries to retrieve information about the server - AString Reply; - const cServer & Server = *cRoot::Get()->GetServer(); - - Printf(Reply, "%s%s%i%s%i", - Server.GetDescription().c_str(), - cChatColor::Delimiter, - Server.GetNumPlayers(), - cChatColor::Delimiter, - Server.GetMaxPlayers() - ); - Kick(Reply); -} - - - - - bool cClientHandle::HandleLogin(UInt32 a_ProtocolVersion, const AString & a_Username) { // If the protocol version hasn't been set yet, set it now: @@ -676,8 +583,8 @@ bool cClientHandle::HandleLogin(UInt32 a_ProtocolVersion, const AString & a_User } // Schedule for authentication; until then, let the player wait (but do not block) - m_State = csAuthenticating; - cRoot::Get()->GetAuthenticator().Authenticate(GetUniqueID(), GetUsername(), m_Protocol->GetAuthServerID()); + SetState(eState::csAuthenticating); + cRoot::Get()->GetAuthenticator().Authenticate(GetUniqueID(), GetUsername(), m_Protocol.GetAuthServerID()); return true; } @@ -762,7 +669,7 @@ void cClientHandle::HandlePlayerAbilities(bool a_CanFly, bool a_IsFlying, float void cClientHandle::HandlePlayerPos(double a_PosX, double a_PosY, double a_PosZ, double a_Stance, bool a_IsOnGround) { - if ((m_Player == nullptr) || (m_State != csPlaying)) + if (m_State != eState::csPlaying) { // The client hasn't been spawned yet and sends nonsense, we know better return; @@ -775,8 +682,7 @@ void cClientHandle::HandlePlayerPos(double a_PosX, double a_PosY, double a_PosZ, } Vector3d NewPosition(a_PosX, a_PosY, a_PosZ); - Vector3d OldPosition = GetPlayer()->GetPosition(); - auto PreviousIsOnGround = GetPlayer()->IsOnGround(); + Vector3d OldPosition = m_Player->GetPosition(); // If the player has moved too far, "repair" them: if ((OldPosition - NewPosition).SqrLength() > 100 * 100) @@ -794,7 +700,13 @@ void cClientHandle::HandlePlayerPos(double a_PosX, double a_PosY, double a_PosZ, // TODO: should do some checks to see if player is not moving through terrain // TODO: Official server refuses position packets too far away from each other, kicking "hacked" clients; we should, too + + if (cChunkDef::BlockToChunk(NewPosition) != cChunkDef::BlockToChunk(m_Player->GetLastPosition())) + { + m_ShouldRefreshSentChunks = true; + } + auto PreviousIsOnGround = m_Player->IsOnGround(); m_Player->SetPosition(NewPosition); m_Player->SetStance(a_Stance); m_Player->SetTouchGround(a_IsOnGround); @@ -996,14 +908,6 @@ void cClientHandle::HandleLeftClick(int a_BlockX, int a_BlockY, int a_BlockZ, eB a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_Status ); - m_NumBlockChangeInteractionsThisTick++; - - if (!CheckBlockInteractionsRate()) - { - Kick("Too many blocks were destroyed per unit time - hacked client?"); - return; - } - if ((a_Status == DIG_STATUS_STARTED) || (a_Status == DIG_STATUS_FINISHED)) { if (a_BlockFace == BLOCK_FACE_NONE) @@ -1042,7 +946,7 @@ void cClientHandle::HandleLeftClick(int a_BlockX, int a_BlockY, int a_BlockZ, eB } } - cPluginManager * PlgMgr = cRoot::Get()->GetPluginManager(); + auto PlgMgr = cRoot::Get()->GetPluginManager(); if (m_Player->IsFrozen() || PlgMgr->CallHookPlayerLeftClick(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, static_cast<char>(a_Status))) { // A plugin doesn't agree with the action, replace the block on the client and quit: @@ -1349,7 +1253,7 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e a_BlockFace = BLOCK_FACE_NONE; } - cPluginManager * PlgMgr = cRoot::Get()->GetPluginManager(); + auto PlgMgr = cRoot::Get()->GetPluginManager(); if (m_Player->IsFrozen() || PlgMgr->CallHookPlayerRightClick(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ)) { // A plugin doesn't agree with the action, replace the block on the client and quit: @@ -1378,14 +1282,6 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e return; } - m_NumBlockChangeInteractionsThisTick++; - - if (!CheckBlockInteractionsRate()) - { - Kick("Too many blocks were placed / interacted with per unit time - hacked client?"); - return; - } - const cItem & Equipped = m_Player->GetInventory().GetEquippedItem(); if ((Equipped.m_ItemType != a_HeldItem.m_ItemType) && (a_HeldItem.m_ItemType != -1)) @@ -1488,6 +1384,7 @@ void cClientHandle::HandleChat(const AString & a_Message) // Not a command, broadcast as a message: cCompositeChat Msg; AString Color = m_Player->GetColor(); + if (Color.length() == 3) { Color = AString("@") + Color[2]; @@ -1496,6 +1393,7 @@ void cClientHandle::HandleChat(const AString & a_Message) { Color.clear(); } + Msg.AddTextPart(AString("<") + m_Player->GetName() + "> ", Color); Msg.ParseText(Message); Msg.UnderlineUrls(); @@ -1508,7 +1406,7 @@ void cClientHandle::HandleChat(const AString & a_Message) void cClientHandle::HandlePlayerLook(float a_Rotation, float a_Pitch, bool a_IsOnGround) { - if ((m_Player == nullptr) || (m_State != csPlaying)) + if (m_State != eState::csPlaying) { return; } @@ -1606,7 +1504,12 @@ void cClientHandle::HandleUpdateSign( if (m_LastPlacedSign.Equals(Vector3i(a_BlockX, a_BlockY, a_BlockZ))) { m_LastPlacedSign.Set(0, -1, 0); - m_Player->GetWorld()->SetSignLines(a_BlockX, a_BlockY, a_BlockZ, a_Line1, a_Line2, a_Line3, a_Line4, m_Player); + m_Player->GetWorld()->QueueTask( + [=](cWorld & a_World) + { + a_World.SetSignLines(a_BlockX, a_BlockY, a_BlockZ, a_Line1, a_Line2, a_Line3, a_Line4, m_Player); + } + ); } } @@ -1684,11 +1587,6 @@ void cClientHandle::HandleUseEntity(UInt32 a_TargetEntityID, bool a_IsLeftClick) void cClientHandle::HandleRespawn(void) { - if (m_Player == nullptr) - { - Destroy(); - return; - } m_Player->Respawn(); cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*m_Player); } @@ -1717,28 +1615,12 @@ bool cClientHandle::CheckMultiLogin(const AString & a_Username) return true; } - // Check if the player is waiting to be transferred to the World. + // Check if the username is connected already: if (cRoot::Get()->GetServer()->IsPlayerInQueue(a_Username)) { Kick("A player of the username is already logged in"); return false; } - - class cCallback : - public cPlayerListCallback - { - virtual bool Item(cPlayer * a_Player) override - { - return true; - } - } Callback; - - // Check if the player is in any World. - if (cRoot::Get()->DoWithPlayer(a_Username, Callback)) - { - Kick("A player of the username is already logged in"); - return false; - } return true; } @@ -1793,9 +1675,9 @@ void cClientHandle::HandleEntityLeaveBed(UInt32 a_EntityID) return; } - cChunkInterface Interface(GetPlayer()->GetWorld()->GetChunkMap()); - cBlockBedHandler::SetBedOccupationState(Interface, GetPlayer()->GetLastBedPos(), false); - GetPlayer()->SetIsInBed(false); + cChunkInterface Interface(m_Player->GetWorld()->GetChunkMap()); + cBlockBedHandler::SetBedOccupationState(Interface, m_Player->GetLastBedPos(), false); + m_Player->SetIsInBed(false); } @@ -1819,10 +1701,6 @@ void cClientHandle::HandleEntitySprinting(UInt32 a_EntityID, bool a_IsSprinting) void cClientHandle::HandleUnmount(void) { - if (m_Player == nullptr) - { - return; - } m_Player->Detach(); } @@ -1833,6 +1711,7 @@ void cClientHandle::HandleUnmount(void) void cClientHandle::HandleTabCompletion(const AString & a_Text) { AStringVector Results; + // Get player name completions. if (cRoot::Get()->GetServer()->ShouldAllowMultiWorldTabCompletion()) { @@ -1843,8 +1722,9 @@ void cClientHandle::HandleTabCompletion(const AString & a_Text) m_Player->GetWorld()->TabCompleteUserName(a_Text, Results); } - // Get command completions. + // Get command completions cRoot::Get()->GetPluginManager()->TabCompleteCommand(a_Text, Results, m_Player); + if (Results.empty()) { return; @@ -1861,12 +1741,6 @@ void cClientHandle::HandleTabCompletion(const AString & a_Text) void cClientHandle::SendData(const char * a_Data, size_t a_Size) { - if (m_HasSentDC) - { - // This could crash the client, because they've already unloaded the world etc., and suddenly a wild packet appears (#31) - return; - } - cCSLock Lock(m_CSOutgoingData); m_OutgoingData.append(a_Data, a_Size); } @@ -1877,92 +1751,131 @@ void cClientHandle::SendData(const char * a_Data, size_t a_Size) void cClientHandle::RemoveFromWorld(void) { + ASSERT(cRoot::Get()->GetServer()->IsInTickThread()); + // Remove all associated chunks: - decltype(m_LoadedChunks) Chunks; + for (const auto & Chunk : m_LoadedChunks) { - cCSLock Lock(m_CSChunkLists); - std::swap(Chunks, m_LoadedChunks); - m_ChunksToSend.clear(); + Chunk.second.get().QueueTask( + [ChunkCoordinates = Chunk.first, ClientHandle = shared_from_this()](cWorld & a_World) + { + a_World.RemoveChunkClient(ChunkCoordinates.m_ChunkX, ChunkCoordinates.m_ChunkZ, ClientHandle); + } + ); + GetProtocol().SendUnloadChunk(Chunk.first.m_ChunkX, Chunk.first.m_ChunkZ); } - for (auto && Chunk : Chunks) - { - m_Protocol->SendUnloadChunk(Chunk.m_ChunkX, Chunk.m_ChunkZ); - } // for itr - Chunks[] - // Here, we set last streamed values to bogus ones so everything is resent - m_LastStreamedChunkX = 0x7fffffff; - m_LastStreamedChunkZ = 0x7fffffff; + m_LoadedChunks.clear(); } -void cClientHandle::InvalidateCachedSentChunk() +bool cClientHandle::EnforceBlockInteractionsRate(void) { - ASSERT(m_Player != nullptr); - // Sets this to a junk value different from the player's current chunk, which invalidates it and - // ensures its value will not be used. - m_CachedSentChunk = cChunkCoords(m_Player->GetChunkX() + 500, m_Player->GetChunkZ()); + ASSERT(cRoot::Get()->GetServer()->IsInTickThread()); + + m_NumBlockChangeInteractionsThisTick++; + if (m_NumBlockChangeInteractionsThisTick > MAX_BLOCK_CHANGE_INTERACTIONS) + { + Kick("Too many blocks were destroyed per unit time - hacked client?"); + return false; + } + + return true; } -bool cClientHandle::IsPlayerChunkSent() +void cClientHandle::ProcessQueuedIncomingData(void) { - return m_HasSentPlayerChunk; + decltype(m_IncomingData) IncomingData; + { + cCSLock Lock(m_CSIncomingData); + std::swap(IncomingData, m_IncomingData); + } + + if (!IncomingData.empty()) + { + m_Protocol.DataReceived(IncomingData.data(), IncomingData.size()); + } } -bool cClientHandle::CheckBlockInteractionsRate(void) +void cClientHandle::ProcessDataCommitQueue(void) { - ASSERT(m_Player != nullptr); - ASSERT(m_Player->GetWorld() != nullptr); + if (m_DataCommitQueue.empty()) + { + return; + } - if (m_NumBlockChangeInteractionsThisTick > MAX_BLOCK_CHANGE_INTERACTIONS) + if ((m_Player == nullptr) || (m_Player->GetWorld() == nullptr)) { - return false; + Kick("Illegal packet sent - player not spawned yet - hacked client?"); + return; } - return true; + decltype(m_DataCommitQueue) DataCommitTasks; + std::swap(DataCommitTasks, m_DataCommitQueue); + + m_Player->GetWorld()->QueueTask( + [DataCommitTasks = std::move(DataCommitTasks), Player = m_Player](cWorld & a_World) + { + if (Player->IsChangingWorlds() || (Player->GetWorld() != &a_World)) + { + // IsChangingWorlds is true: cPlayer::m_World may be updated without warning - cannot handle this. + // cPlayer::m_World is unequal to a_World: player has completed world change - deal with after effect by bailing + return; + } + + for (const auto & Task : DataCommitTasks) + { + Task(); + } + } + ); } -void cClientHandle::Tick(float a_Dt) +void cClientHandle::ProcessQueuedOutgoingData(void) { - // Process received network data: - AString IncomingData; - { - cCSLock Lock(m_CSIncomingData); - std::swap(IncomingData, m_IncomingData); - } - if (!IncomingData.empty()) - { - m_Protocol->DataReceived(IncomingData.data(), IncomingData.size()); - } - - // Send any queued outgoing data: - AString OutgoingData; + decltype(m_OutgoingData) OutgoingData; { cCSLock Lock(m_CSOutgoingData); std::swap(OutgoingData, m_OutgoingData); } + if (!OutgoingData.empty()) { - cTCPLinkPtr Link(m_Link); // Grab a copy of the link in a multithread-safe way - if ((Link != nullptr)) { - Link->Send(OutgoingData.data(), OutgoingData.size()); + cCSLock Lock(m_CSLink); + + // All possible operations which reset m_Link MUST cause the clienthandle to cease to be ticked by the server + ASSERT(m_Link != nullptr); + + m_Link->Send(OutgoingData); } } +} + + + + + +void cClientHandle::Tick(float a_Dt) +{ + ProcessQueuedIncomingData(); + ProcessQueuedOutgoingData(); + ProcessDataCommitQueue(); m_TicksSinceLastPacket += 1; if (m_TicksSinceLastPacket > 600) // 30 seconds time-out @@ -1976,71 +1889,20 @@ void cClientHandle::Tick(float a_Dt) return; } - - // Freeze the player if it is standing on a chunk not yet sent to the client - m_HasSentPlayerChunk = false; - if (m_Player->GetParentChunk() != nullptr) - { - // If the chunk is invalid, do not bother checking if it's sent to the client, it is definitely not - if (m_Player->GetParentChunk()->IsValid()) - { - // Before iterating m_SentChunks, see if the player's coords equal m_CachedSentChunk - // If so, the chunk has been sent to the client. This is an optimization that saves an iteration of m_SentChunks. - if (cChunkCoords(m_Player->GetChunkX(), m_Player->GetChunkZ()) == m_CachedSentChunk) - { - m_HasSentPlayerChunk = true; - } - else - { - // This block is entered only when the player moves to a new chunk, invalidating the cached coords. - // Otherwise the cached coords are used. - cCSLock Lock(m_CSChunkLists); - auto itr = std::find(m_SentChunks.begin(), m_SentChunks.end(), cChunkCoords(m_Player->GetChunkX(), m_Player->GetChunkZ())); - if (itr != m_SentChunks.end()) - { - m_CachedSentChunk = *itr; - m_HasSentPlayerChunk = true; - } - } - } - } - - // If the chunk the player's in was just sent, spawn the player: - if (m_HasSentPlayerChunk && (m_State == csDownloadingWorld)) - { - m_Protocol->SendPlayerMoveLook(); - m_State = csPlaying; - } - // Send a ping packet: - if (m_State == csPlaying) + if ((m_PingStartTime + PING_TIME_MS <= std::chrono::steady_clock::now())) { - if ((m_PingStartTime + PING_TIME_MS <= std::chrono::steady_clock::now())) - { - m_PingID++; - m_PingStartTime = std::chrono::steady_clock::now(); - m_Protocol->SendKeepAlive(m_PingID); - } + m_PingID++; + m_PingStartTime = std::chrono::steady_clock::now(); + m_Protocol.SendKeepAlive(m_PingID); } - if ((m_State >= csAuthenticated) && (m_State < csDestroying)) + if (m_ShouldRefreshSentChunks && (m_State >= eState::csAuthenticated) && (m_State < eState::csDestroying)) { - // Stream 4 chunks per tick - for (int i = 0; i < 4; i++) - { - // Stream the next chunk - if (StreamNextChunk()) - { - // Streaming finished. All chunks are loaded. - break; - } - } + StreamAllChunks(); + UnloadOutOfRangeChunks(); - // Unload all chunks that are out of the view distance (every 5 seconds) - if ((m_Player->GetWorld()->GetWorldAge() % 100) == 0) - { - UnloadOutOfRangeChunks(); - } + m_ShouldRefreshSentChunks = false; } // Handle block break animation: @@ -2067,57 +1929,9 @@ void cClientHandle::Tick(float a_Dt) -void cClientHandle::ServerTick(float a_Dt) -{ - // Process received network data: - AString IncomingData; - { - cCSLock Lock(m_CSIncomingData); - std::swap(IncomingData, m_IncomingData); - } - if (!IncomingData.empty()) - { - m_Protocol->DataReceived(IncomingData.data(), IncomingData.size()); - } - - // Send any queued outgoing data: - AString OutgoingData; - { - cCSLock Lock(m_CSOutgoingData); - std::swap(OutgoingData, m_OutgoingData); - } - if ((m_Link != nullptr) && !OutgoingData.empty()) - { - m_Link->Send(OutgoingData.data(), OutgoingData.size()); - } - - if (m_State == csAuthenticated) - { - StreamNextChunk(); - - // Remove the client handle from the server, it will be ticked from its cPlayer object from now on - cRoot::Get()->GetServer()->ClientMovedToWorld(this); - - // Add the player to the world (start ticking from there): - m_State = csDownloadingWorld; - m_Player->Initialize(*(m_Player->GetWorld())); - return; - } - - m_TicksSinceLastPacket += 1; - if (m_TicksSinceLastPacket > 600) // 30 seconds - { - SendDisconnect("Nooooo!! You timed out! D: Come back!"); - } -} - - - - - void cClientHandle::SendAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle) { - m_Protocol->SendAttachEntity(a_Entity, a_Vehicle); + m_Protocol.SendAttachEntity(a_Entity, a_Vehicle); } @@ -2126,7 +1940,7 @@ void cClientHandle::SendAttachEntity(const cEntity & a_Entity, const cEntity & a void cClientHandle::SendBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType) { - m_Protocol->SendBlockAction(a_BlockX, a_BlockY, a_BlockZ, a_Byte1, a_Byte2, a_BlockType); + m_Protocol.SendBlockAction(a_BlockX, a_BlockY, a_BlockZ, a_Byte1, a_Byte2, a_BlockType); } @@ -2135,7 +1949,7 @@ void cClientHandle::SendBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, ch void cClientHandle::SendBlockBreakAnim(UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) { - m_Protocol->SendBlockBreakAnim(a_EntityID, a_BlockX, a_BlockY, a_BlockZ, a_Stage); + m_Protocol.SendBlockBreakAnim(a_EntityID, a_BlockX, a_BlockY, a_BlockZ, a_Stage); } @@ -2144,17 +1958,7 @@ void cClientHandle::SendBlockBreakAnim(UInt32 a_EntityID, int a_BlockX, int a_Bl void cClientHandle::SendBlockChange(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) { - int ChunkX, ChunkZ = 0; - cChunkDef::BlockToChunk(a_BlockX, a_BlockZ, ChunkX, ChunkZ); - cChunkCoords ChunkCoords = cChunkCoords(ChunkX, ChunkZ); - - // Do not send block changes in chunks that weren't sent to the client yet: - cCSLock Lock(m_CSChunkLists); - if (std::find(m_SentChunks.begin(), m_SentChunks.end(), ChunkCoords) != m_SentChunks.end()) - { - Lock.Unlock(); - m_Protocol->SendBlockChange(a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta); - } + GetProtocol().SendBlockChange(a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta); } @@ -2163,16 +1967,7 @@ void cClientHandle::SendBlockChange(int a_BlockX, int a_BlockY, int a_BlockZ, BL void cClientHandle::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) { - ASSERT(!a_Changes.empty()); // We don't want to be sending empty change packets! - - // Do not send block changes in chunks that weren't sent to the client yet: - cChunkCoords ChunkCoords = cChunkCoords(a_ChunkX, a_ChunkZ); - cCSLock Lock(m_CSChunkLists); - if (std::find(m_SentChunks.begin(), m_SentChunks.end(), ChunkCoords) != m_SentChunks.end()) - { - Lock.Unlock(); - m_Protocol->SendBlockChanges(a_ChunkX, a_ChunkZ, a_Changes); - } + GetProtocol().SendBlockChanges(a_ChunkX, a_ChunkZ, a_Changes); } @@ -2181,19 +1976,9 @@ void cClientHandle::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlock void cClientHandle::SendChat(const AString & a_Message, eMessageType a_ChatPrefix, const AString & a_AdditionalData) { - cWorld * World = GetPlayer()->GetWorld(); - if (World == nullptr) - { - World = cRoot::Get()->GetWorld(GetPlayer()->GetLoadedWorldName()); - if (World == nullptr) - { - World = cRoot::Get()->GetDefaultWorld(); - } - } - - bool ShouldUsePrefixes = World->ShouldUseChatPrefixes(); + bool ShouldUsePrefixes = m_Player->GetWorld()->ShouldUseChatPrefixes(); AString Message = FormatMessageType(ShouldUsePrefixes, a_ChatPrefix, a_AdditionalData); - m_Protocol->SendChat(Message.append(a_Message), ctChatBox, ShouldUsePrefixes); + m_Protocol.SendChat(Message.append(a_Message), ctChatBox, ShouldUsePrefixes); } @@ -2202,18 +1987,8 @@ void cClientHandle::SendChat(const AString & a_Message, eMessageType a_ChatPrefi void cClientHandle::SendChat(const cCompositeChat & a_Message) { - cWorld * World = GetPlayer()->GetWorld(); - if (World == nullptr) - { - World = cRoot::Get()->GetWorld(GetPlayer()->GetLoadedWorldName()); - if (World == nullptr) - { - World = cRoot::Get()->GetDefaultWorld(); - } - } - - bool ShouldUsePrefixes = World->ShouldUseChatPrefixes(); - m_Protocol->SendChat(a_Message, ctChatBox, ShouldUsePrefixes); + bool ShouldUsePrefixes = m_Player->GetWorld()->ShouldUseChatPrefixes(); + m_Protocol.SendChat(a_Message, ctChatBox, ShouldUsePrefixes); } @@ -2222,18 +1997,8 @@ void cClientHandle::SendChat(const cCompositeChat & a_Message) void cClientHandle::SendChatAboveActionBar(const AString & a_Message, eMessageType a_ChatPrefix, const AString & a_AdditionalData) { - cWorld * World = GetPlayer()->GetWorld(); - if (World == nullptr) - { - World = cRoot::Get()->GetWorld(GetPlayer()->GetLoadedWorldName()); - if (World == nullptr) - { - World = cRoot::Get()->GetDefaultWorld(); - } - } - - AString Message = FormatMessageType(World->ShouldUseChatPrefixes(), a_ChatPrefix, a_AdditionalData); - m_Protocol->SendChat(Message.append(a_Message), ctAboveActionBar); + AString Message = FormatMessageType(m_Player->GetWorld()->ShouldUseChatPrefixes(), a_ChatPrefix, a_AdditionalData); + m_Protocol.SendChat(Message.append(a_Message), ctAboveActionBar); } @@ -2242,7 +2007,7 @@ void cClientHandle::SendChatAboveActionBar(const AString & a_Message, eMessageTy void cClientHandle::SendChatAboveActionBar(const cCompositeChat & a_Message) { - m_Protocol->SendChat(a_Message, ctAboveActionBar, GetPlayer()->GetWorld()->ShouldUseChatPrefixes()); + m_Protocol.SendChat(a_Message, ctAboveActionBar, m_Player->GetWorld()->ShouldUseChatPrefixes()); } @@ -2251,19 +2016,9 @@ void cClientHandle::SendChatAboveActionBar(const cCompositeChat & a_Message) void cClientHandle::SendChatSystem(const AString & a_Message, eMessageType a_ChatPrefix, const AString & a_AdditionalData) { - cWorld * World = GetPlayer()->GetWorld(); - if (World == nullptr) - { - World = cRoot::Get()->GetWorld(GetPlayer()->GetLoadedWorldName()); - if (World == nullptr) - { - World = cRoot::Get()->GetDefaultWorld(); - } - } - - auto ShouldUsePrefixes = World->ShouldUseChatPrefixes(); + auto ShouldUsePrefixes = m_Player->GetWorld()->ShouldUseChatPrefixes(); AString Message = FormatMessageType(ShouldUsePrefixes, a_ChatPrefix, a_AdditionalData); - m_Protocol->SendChat(Message.append(a_Message), ctSystem, ShouldUsePrefixes); + m_Protocol.SendChat(Message.append(a_Message), ctSystem, ShouldUsePrefixes); } @@ -2272,49 +2027,80 @@ void cClientHandle::SendChatSystem(const AString & a_Message, eMessageType a_Cha void cClientHandle::SendChatSystem(const cCompositeChat & a_Message) { - m_Protocol->SendChat(a_Message, ctSystem, GetPlayer()->GetWorld()->ShouldUseChatPrefixes()); + m_Protocol.SendChat(a_Message, ctSystem, m_Player->GetWorld()->ShouldUseChatPrefixes()); } -void cClientHandle::SendChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) +void cClientHandle::SendChunkData(cWorld & a_ChunkSenderWorld, const cChunkCoords & a_ChunkCoordinates, cChunkDataSerializer & a_Serializer) { - ASSERT(m_Player != nullptr); + m_Protocol.SendChunkData(a_ChunkCoordinates.m_ChunkX, a_ChunkCoordinates.m_ChunkZ, a_Serializer); - // Check chunks being sent, erase them from m_ChunksToSend: - bool Found = false; - { - cCSLock Lock(m_CSChunkLists); - auto itr = m_ChunksToSend.find(cChunkCoords{a_ChunkX, a_ChunkZ}); - if (itr != m_ChunksToSend.end()) + cRoot::Get()->GetServer()->QueueTask( + [&a_ChunkSenderWorld, a_ChunkCoordinates, ClientHandle = shared_from_this()] { - m_ChunksToSend.erase(itr); - Found = true; - } - } - if (!Found) - { - // This just sometimes happens. If you have a reliably replicatable situation for this, go ahead and fix it - // It's not a big issue anyway, just means that some chunks may be compressed several times - // LOGD("Refusing to send chunk [%d, %d] to client \"%s\" at [%d, %d].", ChunkX, ChunkZ, m_Username.c_str(), m_Player->GetChunkX(), m_Player->GetChunkZ()); - return; - } - - if (m_Protocol == nullptr) - { - // TODO (#2588): investigate if and why this occurs - return; - } + // Add the chunk to the list of chunks sent to the player: + auto Result = ClientHandle->m_LoadedChunks.emplace(a_ChunkCoordinates, std::ref(a_ChunkSenderWorld)); + if (Result.second || (&Result.first->second.get() != &a_ChunkSenderWorld)) + { + auto Player = ClientHandle->GetPlayer(); + if ( + (Player->GetChunkX() == a_ChunkCoordinates.m_ChunkX) && + (Player->GetChunkZ() == a_ChunkCoordinates.m_ChunkZ) + ) + { + if (ClientHandle->m_State == eState::csAuthenticated) + { + ClientHandle->SendPlayerMoveLook(); + ClientHandle->SetState(eState::csPlaying); + } + + Player->GetWorld()->QueueTask( + [Player](cWorld & a_World) + { + auto SearchingPosition = Player->GetPosition(); + if (SearchingPosition.y < 0) + { + SearchingPosition.y = 0; + } + + while (SearchingPosition.y < cChunkDef::Height - 2) + { + // If we find a position with enough space for the player + if (a_World.GetBlock(SearchingPosition.x, SearchingPosition.y, SearchingPosition.z) == E_BLOCK_AIR) + { + if (a_World.GetBlock(SearchingPosition.x, SearchingPosition.y + 1, SearchingPosition.z) == E_BLOCK_AIR) + { + Player->TeleportToCoords(SearchingPosition); + break; + } + else + { + SearchingPosition.y += 2; + } + } + else + { + SearchingPosition.y++; + } + } + } + ); + } - m_Protocol->SendChunkData(a_ChunkX, a_ChunkZ, a_Serializer); + Result.first->second.get().QueueTask( + [a_ChunkCoordinates, ClientHandle](cWorld & a_World) + { + a_World.RemoveChunkClient(a_ChunkCoordinates.m_ChunkX, a_ChunkCoordinates.m_ChunkZ, ClientHandle); + } + ); - // Add the chunk to the list of chunks sent to the player: - { - cCSLock Lock(m_CSChunkLists); - m_SentChunks.push_back(cChunkCoords(a_ChunkX, a_ChunkZ)); - } + Result.first->second = std::ref(a_ChunkSenderWorld); + } + } + ); } @@ -2323,7 +2109,7 @@ void cClientHandle::SendChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataSerializ void cClientHandle::SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player) { - m_Protocol->SendCollectEntity(a_Entity, a_Player); + m_Protocol.SendCollectEntity(a_Entity, a_Player); } @@ -2332,7 +2118,7 @@ void cClientHandle::SendCollectEntity(const cEntity & a_Entity, const cPlayer & void cClientHandle::SendDestroyEntity(const cEntity & a_Entity) { - m_Protocol->SendDestroyEntity(a_Entity); + m_Protocol.SendDestroyEntity(a_Entity); } @@ -2341,7 +2127,7 @@ void cClientHandle::SendDestroyEntity(const cEntity & a_Entity) void cClientHandle::SendDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle) { - m_Protocol->SendDetachEntity(a_Entity, a_PreviousVehicle); + m_Protocol.SendDetachEntity(a_Entity, a_PreviousVehicle); } @@ -2350,14 +2136,27 @@ void cClientHandle::SendDetachEntity(const cEntity & a_Entity, const cEntity & a void cClientHandle::SendDisconnect(const AString & a_Reason) { - // Destruction (Destroy()) is called when the client disconnects, not when a disconnect packet (or anything else) is sent - // Otherwise, the cClientHandle instance is can be unexpectedly removed from the associated player - Core/#142 - if (!m_HasSentDC) + // Lemma 1: All outgoing data is queued to be sent on the server tick thread. + // Lemma 2: All link-closing actions will set state to destroyed. + // Lemma 3: A call to Destroy() is guaranteed to prevent the send queue from being processed. + // Corollary: Prevents situations where the client was signalled to disconnect - Kick() - and suddenly a wild packet appears (#31) + // Note: Kick packets are sent explicitly by flushing the queue; TODO: probably fine? + + LOGD("Sending a DC: \"%s\"", StripColorCodes(a_Reason).c_str()); + { - LOGD("Sending a DC: \"%s\"", StripColorCodes(a_Reason).c_str()); - m_Protocol->SendDisconnect(a_Reason); - m_HasSentDC = true; + cCSLock Lock(m_CSLink); + m_OutgoingData.clear(); + m_Protocol.SendDisconnect(a_Reason); + ProcessQueuedOutgoingData(); } + + cRoot::Get()->GetServer()->QueueTask( + [ClientHandle = shared_from_this()] + { + ClientHandle->Destroy(); + } + ); } @@ -2367,7 +2166,7 @@ void cClientHandle::SendDisconnect(const AString & a_Reason) void cClientHandle::SendEditSign(int a_BlockX, int a_BlockY, int a_BlockZ) { m_LastPlacedSign.Set(a_BlockX, a_BlockY, a_BlockZ); - m_Protocol->SendEditSign(a_BlockX, a_BlockY, a_BlockZ); + m_Protocol.SendEditSign(a_BlockX, a_BlockY, a_BlockZ); } @@ -2376,7 +2175,7 @@ void cClientHandle::SendEditSign(int a_BlockX, int a_BlockY, int a_BlockZ) void cClientHandle::SendEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration) { - m_Protocol->SendEntityEffect(a_Entity, a_EffectID, a_Amplifier, a_Duration); + m_Protocol.SendEntityEffect(a_Entity, a_EffectID, a_Amplifier, a_Duration); } @@ -2385,7 +2184,7 @@ void cClientHandle::SendEntityEffect(const cEntity & a_Entity, int a_EffectID, i void cClientHandle::SendEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item) { - m_Protocol->SendEntityEquipment(a_Entity, a_SlotNum, a_Item); + m_Protocol.SendEntityEquipment(a_Entity, a_SlotNum, a_Item); } @@ -2396,7 +2195,7 @@ void cClientHandle::SendEntityHeadLook(const cEntity & a_Entity) { ASSERT(a_Entity.GetUniqueID() != m_Player->GetUniqueID()); // Must not send for self - m_Protocol->SendEntityHeadLook(a_Entity); + m_Protocol.SendEntityHeadLook(a_Entity); } @@ -2407,7 +2206,7 @@ void cClientHandle::SendEntityLook(const cEntity & a_Entity) { ASSERT(a_Entity.GetUniqueID() != m_Player->GetUniqueID()); // Must not send for self - m_Protocol->SendEntityLook(a_Entity); + m_Protocol.SendEntityLook(a_Entity); } @@ -2416,7 +2215,7 @@ void cClientHandle::SendEntityLook(const cEntity & a_Entity) void cClientHandle::SendEntityMetadata(const cEntity & a_Entity) { - m_Protocol->SendEntityMetadata(a_Entity); + m_Protocol.SendEntityMetadata(a_Entity); } @@ -2427,7 +2226,7 @@ void cClientHandle::SendEntityRelMove(const cEntity & a_Entity, char a_RelX, cha { ASSERT(a_Entity.GetUniqueID() != m_Player->GetUniqueID()); // Must not send for self - m_Protocol->SendEntityRelMove(a_Entity, a_RelX, a_RelY, a_RelZ); + m_Protocol.SendEntityRelMove(a_Entity, a_RelX, a_RelY, a_RelZ); } @@ -2438,7 +2237,7 @@ void cClientHandle::SendEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, { ASSERT(a_Entity.GetUniqueID() != m_Player->GetUniqueID()); // Must not send for self - m_Protocol->SendEntityRelMoveLook(a_Entity, a_RelX, a_RelY, a_RelZ); + m_Protocol.SendEntityRelMoveLook(a_Entity, a_RelX, a_RelY, a_RelZ); } @@ -2447,7 +2246,7 @@ void cClientHandle::SendEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, void cClientHandle::SendEntityStatus(const cEntity & a_Entity, char a_Status) { - m_Protocol->SendEntityStatus(a_Entity, a_Status); + m_Protocol.SendEntityStatus(a_Entity, a_Status); } @@ -2456,7 +2255,7 @@ void cClientHandle::SendEntityStatus(const cEntity & a_Entity, char a_Status) void cClientHandle::SendEntityVelocity(const cEntity & a_Entity) { - m_Protocol->SendEntityVelocity(a_Entity); + m_Protocol.SendEntityVelocity(a_Entity); } @@ -2474,7 +2273,7 @@ void cClientHandle::SendExplosion(double a_BlockX, double a_BlockY, double a_Blo // Update the statistics: m_NumExplosionsThisTick++; - m_Protocol->SendExplosion(a_BlockX, a_BlockY, a_BlockZ, a_Radius, a_BlocksAffected, a_PlayerMotion); + m_Protocol.SendExplosion(a_BlockX, a_BlockY, a_BlockZ, a_Radius, a_BlocksAffected, a_PlayerMotion); } @@ -2483,7 +2282,7 @@ void cClientHandle::SendExplosion(double a_BlockX, double a_BlockY, double a_Blo void cClientHandle::SendGameMode(eGameMode a_GameMode) { - m_Protocol->SendGameMode(a_GameMode); + m_Protocol.SendGameMode(a_GameMode); } @@ -2492,7 +2291,7 @@ void cClientHandle::SendGameMode(eGameMode a_GameMode) void cClientHandle::SendHealth(void) { - m_Protocol->SendHealth(); + m_Protocol.SendHealth(); } @@ -2501,7 +2300,7 @@ void cClientHandle::SendHealth(void) void cClientHandle::SendHideTitle(void) { - m_Protocol->SendHideTitle(); + m_Protocol.SendHideTitle(); } @@ -2510,7 +2309,7 @@ void cClientHandle::SendHideTitle(void) void cClientHandle::SendInventorySlot(char a_WindowID, short a_SlotNum, const cItem & a_Item) { - m_Protocol->SendInventorySlot(a_WindowID, a_SlotNum, a_Item); + m_Protocol.SendInventorySlot(a_WindowID, a_SlotNum, a_Item); } @@ -2519,7 +2318,7 @@ void cClientHandle::SendInventorySlot(char a_WindowID, short a_SlotNum, const cI void cClientHandle::SendMapData(const cMap & a_Map, int a_DataStartX, int a_DataStartY) { - m_Protocol->SendMapData(a_Map, a_DataStartX, a_DataStartY); + m_Protocol.SendMapData(a_Map, a_DataStartX, a_DataStartY); } @@ -2528,7 +2327,7 @@ void cClientHandle::SendMapData(const cMap & a_Map, int a_DataStartX, int a_Data void cClientHandle::SendParticleEffect(const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount) { - m_Protocol->SendParticleEffect(a_ParticleName, a_SrcX, a_SrcY, a_SrcZ, a_OffsetX, a_OffsetY, a_OffsetZ, a_ParticleData, a_ParticleAmount); + m_Protocol.SendParticleEffect(a_ParticleName, a_SrcX, a_SrcY, a_SrcZ, a_OffsetX, a_OffsetY, a_OffsetZ, a_ParticleData, a_ParticleAmount); } @@ -2537,7 +2336,7 @@ void cClientHandle::SendParticleEffect(const AString & a_ParticleName, float a_S void cClientHandle::SendParticleEffect(const AString & a_ParticleName, const Vector3f a_Src, const Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array<int, 2> a_Data) { - m_Protocol->SendParticleEffect(a_ParticleName, a_Src, a_Offset, a_ParticleData, a_ParticleAmount, a_Data); + m_Protocol.SendParticleEffect(a_ParticleName, a_Src, a_Offset, a_ParticleData, a_ParticleAmount, a_Data); } @@ -2546,7 +2345,7 @@ void cClientHandle::SendParticleEffect(const AString & a_ParticleName, const Vec void cClientHandle::SendPickupSpawn(const cPickup & a_Pickup) { - m_Protocol->SendPickupSpawn(a_Pickup); + m_Protocol.SendPickupSpawn(a_Pickup); } @@ -2554,7 +2353,7 @@ void cClientHandle::SendPickupSpawn(const cPickup & a_Pickup) void cClientHandle::SendPaintingSpawn(const cPainting & a_Painting) { - m_Protocol->SendPaintingSpawn(a_Painting); + m_Protocol.SendPaintingSpawn(a_Painting); } @@ -2563,7 +2362,7 @@ void cClientHandle::SendPaintingSpawn(const cPainting & a_Painting) void cClientHandle::SendEntityAnimation(const cEntity & a_Entity, char a_Animation) { - m_Protocol->SendEntityAnimation(a_Entity, a_Animation); + m_Protocol.SendEntityAnimation(a_Entity, a_Animation); } @@ -2572,7 +2371,7 @@ void cClientHandle::SendEntityAnimation(const cEntity & a_Entity, char a_Animati void cClientHandle::SendPlayerAbilities() { - m_Protocol->SendPlayerAbilities(); + m_Protocol.SendPlayerAbilities(); } @@ -2581,7 +2380,7 @@ void cClientHandle::SendPlayerAbilities() void cClientHandle::SendPlayerListAddPlayer(const cPlayer & a_Player) { - m_Protocol->SendPlayerListAddPlayer(a_Player); + m_Protocol.SendPlayerListAddPlayer(a_Player); } @@ -2590,7 +2389,7 @@ void cClientHandle::SendPlayerListAddPlayer(const cPlayer & a_Player) void cClientHandle::SendPlayerListRemovePlayer(const cPlayer & a_Player) { - m_Protocol->SendPlayerListRemovePlayer(a_Player); + m_Protocol.SendPlayerListRemovePlayer(a_Player); } @@ -2599,7 +2398,7 @@ void cClientHandle::SendPlayerListRemovePlayer(const cPlayer & a_Player) void cClientHandle::SendPlayerListUpdateGameMode(const cPlayer & a_Player) { - m_Protocol->SendPlayerListUpdateGameMode(a_Player); + m_Protocol.SendPlayerListUpdateGameMode(a_Player); } @@ -2608,7 +2407,7 @@ void cClientHandle::SendPlayerListUpdateGameMode(const cPlayer & a_Player) void cClientHandle::SendPlayerListUpdatePing(const cPlayer & a_Player) { - m_Protocol->SendPlayerListUpdatePing(a_Player); + m_Protocol.SendPlayerListUpdatePing(a_Player); } @@ -2617,7 +2416,7 @@ void cClientHandle::SendPlayerListUpdatePing(const cPlayer & a_Player) void cClientHandle::SendPlayerListUpdateDisplayName(const cPlayer & a_Player, const AString & a_CustomName) { - m_Protocol->SendPlayerListUpdateDisplayName(a_Player, a_CustomName); + m_Protocol.SendPlayerListUpdateDisplayName(a_Player, a_CustomName); } @@ -2626,7 +2425,7 @@ void cClientHandle::SendPlayerListUpdateDisplayName(const cPlayer & a_Player, co void cClientHandle::SendPlayerMaxSpeed(void) { - m_Protocol->SendPlayerMaxSpeed(); + m_Protocol.SendPlayerMaxSpeed(); } @@ -2640,7 +2439,7 @@ void cClientHandle::SendPlayerMoveLook(void) m_Player->GetPosX(), m_Player->GetPosY(), m_Player->GetPosZ(), m_Player->GetStance(), m_Player->IsOnGround() ? 1 : 0 ); */ - m_Protocol->SendPlayerMoveLook(); + m_Protocol.SendPlayerMoveLook(); } @@ -2649,7 +2448,7 @@ void cClientHandle::SendPlayerMoveLook(void) void cClientHandle::SendPlayerPosition(void) { - m_Protocol->SendPlayerPosition(); + m_Protocol.SendPlayerPosition(); } @@ -2665,10 +2464,10 @@ void cClientHandle::SendPlayerSpawn(const cPlayer & a_Player) } LOGD("Spawning player \"%s\" on client \"%s\" @ %s", - a_Player.GetName().c_str(), GetPlayer()->GetName().c_str(), GetIPString().c_str() + a_Player.GetName().c_str(), m_Player->GetName().c_str(), GetIPString().c_str() ); - m_Protocol->SendPlayerSpawn(a_Player); + m_Protocol.SendPlayerSpawn(a_Player); } @@ -2677,7 +2476,7 @@ void cClientHandle::SendPlayerSpawn(const cPlayer & a_Player) void cClientHandle::SendPluginMessage(const AString & a_Channel, const AString & a_Message) { - m_Protocol->SendPluginMessage(a_Channel, a_Message); + m_Protocol.SendPluginMessage(a_Channel, a_Message); } @@ -2686,7 +2485,7 @@ void cClientHandle::SendPluginMessage(const AString & a_Channel, const AString & void cClientHandle::SendRemoveEntityEffect(const cEntity & a_Entity, int a_EffectID) { - m_Protocol->SendRemoveEntityEffect(a_Entity, a_EffectID); + m_Protocol.SendRemoveEntityEffect(a_Entity, a_EffectID); } @@ -2695,7 +2494,7 @@ void cClientHandle::SendRemoveEntityEffect(const cEntity & a_Entity, int a_Effec void cClientHandle::SendResetTitle() { - m_Protocol->SendResetTitle(); + m_Protocol.SendResetTitle(); } @@ -2704,7 +2503,7 @@ void cClientHandle::SendResetTitle() void cClientHandle::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) { - m_Protocol->SendRespawn(a_Dimension, a_ShouldIgnoreDimensionChecks); + m_Protocol.SendRespawn(a_Dimension, a_ShouldIgnoreDimensionChecks); } @@ -2713,7 +2512,7 @@ void cClientHandle::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimen void cClientHandle::SendExperience(void) { - m_Protocol->SendExperience(); + m_Protocol.SendExperience(); } @@ -2722,7 +2521,7 @@ void cClientHandle::SendExperience(void) void cClientHandle::SendExperienceOrb(const cExpOrb & a_ExpOrb) { - m_Protocol->SendExperienceOrb(a_ExpOrb); + m_Protocol.SendExperienceOrb(a_ExpOrb); } @@ -2731,7 +2530,7 @@ void cClientHandle::SendExperienceOrb(const cExpOrb & a_ExpOrb) void cClientHandle::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) { - m_Protocol->SendScoreboardObjective(a_Name, a_DisplayName, a_Mode); + m_Protocol.SendScoreboardObjective(a_Name, a_DisplayName, a_Mode); } @@ -2740,7 +2539,7 @@ void cClientHandle::SendScoreboardObjective(const AString & a_Name, const AStrin void cClientHandle::SendScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) { - m_Protocol->SendScoreUpdate(a_Objective, a_Player, a_Score, a_Mode); + m_Protocol.SendScoreUpdate(a_Objective, a_Player, a_Score, a_Mode); } @@ -2749,7 +2548,7 @@ void cClientHandle::SendScoreUpdate(const AString & a_Objective, const AString & void cClientHandle::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) { - m_Protocol->SendDisplayObjective(a_Objective, a_Display); + m_Protocol.SendDisplayObjective(a_Objective, a_Display); } @@ -2758,7 +2557,7 @@ void cClientHandle::SendDisplayObjective(const AString & a_Objective, cScoreboar void cClientHandle::SendSetSubTitle(const cCompositeChat & a_SubTitle) { - m_Protocol->SendSetSubTitle(a_SubTitle); + m_Protocol.SendSetSubTitle(a_SubTitle); } @@ -2767,7 +2566,7 @@ void cClientHandle::SendSetSubTitle(const cCompositeChat & a_SubTitle) void cClientHandle::SendSetRawSubTitle(const AString & a_SubTitle) { - m_Protocol->SendSetRawSubTitle(a_SubTitle); + m_Protocol.SendSetRawSubTitle(a_SubTitle); } @@ -2776,7 +2575,7 @@ void cClientHandle::SendSetRawSubTitle(const AString & a_SubTitle) void cClientHandle::SendSetTitle(const cCompositeChat & a_Title) { - m_Protocol->SendSetTitle(a_Title); + m_Protocol.SendSetTitle(a_Title); } @@ -2785,7 +2584,7 @@ void cClientHandle::SendSetTitle(const cCompositeChat & a_Title) void cClientHandle::SendSetRawTitle(const AString & a_Title) { - m_Protocol->SendSetRawTitle(a_Title); + m_Protocol.SendSetRawTitle(a_Title); } @@ -2794,7 +2593,7 @@ void cClientHandle::SendSetRawTitle(const AString & a_Title) void cClientHandle::SendSoundEffect(const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch) { - m_Protocol->SendSoundEffect(a_SoundName, a_X, a_Y, a_Z, a_Volume, a_Pitch); + m_Protocol.SendSoundEffect(a_SoundName, a_X, a_Y, a_Z, a_Volume, a_Pitch); } @@ -2803,7 +2602,7 @@ void cClientHandle::SendSoundEffect(const AString & a_SoundName, double a_X, dou void cClientHandle::SendSoundParticleEffect(const EffectID a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data) { - m_Protocol->SendSoundParticleEffect(a_EffectID, a_SrcX, a_SrcY, a_SrcZ, a_Data); + m_Protocol.SendSoundParticleEffect(a_EffectID, a_SrcX, a_SrcY, a_SrcZ, a_Data); } @@ -2812,7 +2611,7 @@ void cClientHandle::SendSoundParticleEffect(const EffectID a_EffectID, int a_Src void cClientHandle::SendSpawnFallingBlock(const cFallingBlock & a_FallingBlock) { - m_Protocol->SendSpawnFallingBlock(a_FallingBlock); + m_Protocol.SendSpawnFallingBlock(a_FallingBlock); } @@ -2821,7 +2620,7 @@ void cClientHandle::SendSpawnFallingBlock(const cFallingBlock & a_FallingBlock) void cClientHandle::SendSpawnMob(const cMonster & a_Mob) { - m_Protocol->SendSpawnMob(a_Mob); + m_Protocol.SendSpawnMob(a_Mob); } @@ -2830,7 +2629,7 @@ void cClientHandle::SendSpawnMob(const cMonster & a_Mob) void cClientHandle::SendSpawnObject(const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch) { - m_Protocol->SendSpawnObject(a_Entity, a_ObjectType, a_ObjectData, a_Yaw, a_Pitch); + m_Protocol.SendSpawnObject(a_Entity, a_ObjectType, a_ObjectData, a_Yaw, a_Pitch); } @@ -2839,7 +2638,7 @@ void cClientHandle::SendSpawnObject(const cEntity & a_Entity, char a_ObjectType, void cClientHandle::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) // VehicleSubType is specific to Minecarts { - m_Protocol->SendSpawnVehicle(a_Vehicle, a_VehicleType, a_VehicleSubType); + m_Protocol.SendSpawnVehicle(a_Vehicle, a_VehicleType, a_VehicleSubType); } @@ -2848,7 +2647,7 @@ void cClientHandle::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleTy void cClientHandle::SendStatistics(const cStatManager & a_Manager) { - m_Protocol->SendStatistics(a_Manager); + m_Protocol.SendStatistics(a_Manager); } @@ -2857,7 +2656,7 @@ void cClientHandle::SendStatistics(const cStatManager & a_Manager) void cClientHandle::SendTabCompletionResults(const AStringVector & a_Results) { - m_Protocol->SendTabCompletionResults(a_Results); + m_Protocol.SendTabCompletionResults(a_Results); } @@ -2866,7 +2665,7 @@ void cClientHandle::SendTabCompletionResults(const AStringVector & a_Results) void cClientHandle::SendTeleportEntity(const cEntity & a_Entity) { - m_Protocol->SendTeleportEntity(a_Entity); + m_Protocol.SendTeleportEntity(a_Entity); } @@ -2875,7 +2674,7 @@ void cClientHandle::SendTeleportEntity(const cEntity & a_Entity) void cClientHandle::SendThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ) { - m_Protocol->SendThunderbolt(a_BlockX, a_BlockY, a_BlockZ); + m_Protocol.SendThunderbolt(a_BlockX, a_BlockY, a_BlockZ); } @@ -2884,7 +2683,7 @@ void cClientHandle::SendThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ) void cClientHandle::SendTitleTimes(int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks) { - m_Protocol->SendTitleTimes(a_FadeInTicks, a_DisplayTicks, a_FadeOutTicks); + m_Protocol.SendTitleTimes(a_FadeInTicks, a_DisplayTicks, a_FadeOutTicks); } @@ -2893,22 +2692,7 @@ void cClientHandle::SendTitleTimes(int a_FadeInTicks, int a_DisplayTicks, int a_ void cClientHandle::SendTimeUpdate(Int64 a_WorldAge, Int64 a_TimeOfDay, bool a_DoDaylightCycle) { - m_Protocol->SendTimeUpdate(a_WorldAge, a_TimeOfDay, a_DoDaylightCycle); -} - - - - - -void cClientHandle::SendUnloadChunk(int a_ChunkX, int a_ChunkZ) -{ - // Remove the chunk from the list of chunks sent to the client: - { - cCSLock Lock(m_CSChunkLists); - m_SentChunks.remove(cChunkCoords(a_ChunkX, a_ChunkZ)); - } - - m_Protocol->SendUnloadChunk(a_ChunkX, a_ChunkZ); + m_Protocol.SendTimeUpdate(a_WorldAge, a_TimeOfDay, a_DoDaylightCycle); } @@ -2916,7 +2700,7 @@ void cClientHandle::SendUnloadChunk(int a_ChunkX, int a_ChunkZ) void cClientHandle::SendUpdateBlockEntity(cBlockEntity & a_BlockEntity) { - m_Protocol->SendUpdateBlockEntity(a_BlockEntity); + m_Protocol.SendUpdateBlockEntity(a_BlockEntity); } @@ -2928,7 +2712,7 @@ void cClientHandle::SendUpdateSign( const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4 ) { - m_Protocol->SendUpdateSign( + m_Protocol.SendUpdateSign( a_BlockX, a_BlockY, a_BlockZ, a_Line1, a_Line2, a_Line3, a_Line4 ); @@ -2940,7 +2724,7 @@ void cClientHandle::SendUpdateSign( void cClientHandle::SendUseBed(const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) { - m_Protocol->SendUseBed(a_Entity, a_BlockX, a_BlockY, a_BlockZ); + m_Protocol.SendUseBed(a_Entity, a_BlockX, a_BlockY, a_BlockZ); } @@ -2948,7 +2732,7 @@ void cClientHandle::SendUseBed(const cEntity & a_Entity, int a_BlockX, int a_Blo void cClientHandle::SendWeather(eWeather a_Weather) { - m_Protocol->SendWeather(a_Weather); + m_Protocol.SendWeather(a_Weather); } @@ -2957,7 +2741,7 @@ void cClientHandle::SendWeather(eWeather a_Weather) void cClientHandle::SendWholeInventory(const cWindow & a_Window) { - m_Protocol->SendWholeInventory(a_Window); + m_Protocol.SendWholeInventory(a_Window); } @@ -2966,7 +2750,7 @@ void cClientHandle::SendWholeInventory(const cWindow & a_Window) void cClientHandle::SendWindowClose(const cWindow & a_Window) { - m_Protocol->SendWindowClose(a_Window); + m_Protocol.SendWindowClose(a_Window); } @@ -2975,7 +2759,7 @@ void cClientHandle::SendWindowClose(const cWindow & a_Window) void cClientHandle::SendWindowOpen(const cWindow & a_Window) { - m_Protocol->SendWindowOpen(a_Window); + m_Protocol.SendWindowOpen(a_Window); } @@ -2984,7 +2768,7 @@ void cClientHandle::SendWindowOpen(const cWindow & a_Window) void cClientHandle::SendWindowProperty(const cWindow & a_Window, short a_Property, short a_Value) { - m_Protocol->SendWindowProperty(a_Window, a_Property, a_Value); + m_Protocol.SendWindowProperty(a_Window, a_Property, a_Value); } @@ -3011,15 +2795,19 @@ void cClientHandle::SetUsername( const AString & a_Username) void cClientHandle::SetViewDistance(int a_ViewDistance) { + ASSERT(cRoot::Get()->GetServer()->IsInTickThread()); + ASSERT(m_Player != nullptr); + m_RequestedViewDistance = a_ViewDistance; LOGD("%s is requesting ViewDistance of %d!", GetUsername().c_str(), m_RequestedViewDistance); - // Set the current view distance based on the requested VD and world max VD: - cWorld * world = m_Player->GetWorld(); - if (world != nullptr) - { - m_CurrentViewDistance = Clamp(a_ViewDistance, cClientHandle::MIN_VIEW_DISTANCE, world->GetMaxViewDistance()); - } + cWorld * World = m_Player->GetWorld(); + World->QueueTask( + [a_ViewDistance, ClientHandle = shared_from_this()](cWorld & a_World) + { + ClientHandle->m_CurrentViewDistance = Clamp(a_ViewDistance, cClientHandle::MIN_VIEW_DISTANCE, a_World.GetMaxViewDistance()); + } + ); } @@ -3035,40 +2823,6 @@ bool cClientHandle::HasPluginChannel(const AString & a_PluginChannel) -bool cClientHandle::WantsSendChunk(int a_ChunkX, int a_ChunkZ) -{ - if (m_State >= csDestroying) - { - return false; - } - - cCSLock Lock(m_CSChunkLists); - return m_ChunksToSend.find(cChunkCoords(a_ChunkX, a_ChunkZ)) != m_ChunksToSend.end(); -} - - - - - -void cClientHandle::AddWantedChunk(int a_ChunkX, int a_ChunkZ) -{ - if (m_State >= csDestroying) - { - return; - } - - LOGD("Adding chunk [%d, %d] to wanted chunks for client %p", a_ChunkX, a_ChunkZ, static_cast<void *>(this)); - cCSLock Lock(m_CSChunkLists); - if (m_ChunksToSend.find(cChunkCoords(a_ChunkX, a_ChunkZ)) == m_ChunksToSend.end()) - { - m_ChunksToSend.emplace(a_ChunkX, a_ChunkZ); - } -} - - - - - void cClientHandle::PacketBufferFull(void) { // Too much data in the incoming queue, the server is probably too busy, kick the client: @@ -3107,33 +2861,18 @@ void cClientHandle::SocketClosed(void) { // The socket has been closed for any reason - if (!m_Username.empty()) // Ignore client pings - { - LOGD("Client %s @ %s disconnected", m_Username.c_str(), m_IPString.c_str()); - cRoot::Get()->GetPluginManager()->CallHookDisconnect(*this, "Player disconnected"); - } - if (m_Player != nullptr) - { - m_Player->GetWorld()->QueueTask([this](cWorld & World) + cRoot::Get()->GetServer()->QueueTask( + [ClientHandle = shared_from_this()] { - UNUSED(World); - Destroy(); - }); - } - else - { - Destroy(); - } -} - - - - + if (!ClientHandle->GetUsername().empty()) // Ignore client pings + { + LOGD("Client %s @ %s disconnected", ClientHandle->GetUsername().c_str(), ClientHandle->GetIPString().c_str()); + cRoot::Get()->GetPluginManager()->CallHookDisconnect(*ClientHandle, "Player disconnected"); + } -void cClientHandle::SetSelf(cClientHandlePtr a_Self) -{ - ASSERT(m_Self == nullptr); - m_Self = a_Self; + ClientHandle->Destroy(); + } + ); } @@ -3142,6 +2881,7 @@ void cClientHandle::SetSelf(cClientHandlePtr a_Self) void cClientHandle::OnLinkCreated(cTCPLinkPtr a_Link) { + cCSLock Lock(m_CSLink); m_Link = a_Link; } @@ -3165,10 +2905,6 @@ void cClientHandle::OnReceivedData(const char * a_Data, size_t a_Length) void cClientHandle::OnRemoteClosed(void) { - { - cCSLock Lock(m_CSOutgoingData); - m_Link.reset(); - } SocketClosed(); } @@ -3181,10 +2917,6 @@ void cClientHandle::OnError(int a_ErrorCode, const AString & a_ErrorMsg) LOGD("An error has occurred on client link for %s @ %s: %d (%s). Client disconnected.", m_Username.c_str(), m_IPString.c_str(), a_ErrorCode, a_ErrorMsg.c_str() ); - { - cCSLock Lock(m_CSOutgoingData); - m_Link.reset(); - } SocketClosed(); } diff --git a/src/ClientHandle.h b/src/ClientHandle.h index c49de647f..78caa9532 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -21,13 +21,15 @@ #include "json/json.h" #include "ChunkSender.h" #include "EffectID.h" - +#include "Protocol/ProtocolRecognizer.h" #include <array> #include <atomic> +#include <unordered_map> // fwd: +class cChunk; class cChunkDataSerializer; class cInventory; class cMonster; @@ -36,7 +38,6 @@ class cExpOrb; class cPainting; class cPickup; class cPlayer; -class cProtocol; class cWindow; class cFallingBlock; class cItemHandler; @@ -44,14 +45,14 @@ class cWorld; class cCompositeChat; class cStatManager; class cClientHandle; -typedef SharedPtr<cClientHandle> cClientHandlePtr; -class cClientHandle // tolua_export - : public cTCPLink::cCallbacks +class cClientHandle : // tolua_export + public std::enable_shared_from_this<cClientHandle>, + public cTCPLink::cCallbacks { // tolua_export public: // tolua_export @@ -63,6 +64,17 @@ public: // tolua_export static const int MAX_VIEW_DISTANCE = 32; static const int MIN_VIEW_DISTANCE = 1; + enum class eState + { + csConnected, ///< The client has just connected, waiting for their handshake / login + csAuthenticating, ///< The client has logged in, waiting for external authentication + csAuthenticated, ///< The client has been authenticated, will start streaming chunks in the next tick + csConfirmingPos, ///< The client has been sent the position packet, waiting for them to repeat the position back + csPlaying, ///< Normal gameplay + csDestroying, ///< The client is being destroyed, don't queue any more packets / don't add to chunks + csDestroyed, ///< The client has been destroyed, the destructor is to be called from the owner thread + }; + /** Creates a new client with the specified IP address in its description and the specified initial view distance. */ cClientHandle(const AString & a_IPString, int a_ViewDistance); @@ -86,6 +98,11 @@ public: // tolua_export const Json::Value & GetProperties(void) const { return m_Properties; } + /** Atomically sets the internal client handle state. + Allows only a consecutive state to be set. + Returns if the state was set or not. */ + bool SetState(eState a_State); + /** Sets the player's properties, such as skin image and signature. Used mainly by BungeeCord compatibility code - property querying is done on the BungeeCord server and the results are passed to MCS running in offline mode. */ @@ -119,43 +136,48 @@ public: // tolua_export /** Authenticates the specified user, called by cAuthenticator */ void Authenticate(const AString & a_Name, const AString & a_UUID, const Json::Value & a_Properties); - /** This function sends a new unloaded chunk to the player. Returns true if all chunks are loaded. */ - bool StreamNextChunk(); + /** Send all necessary chunks to the player */ + void StreamAllChunks(); /** Remove all loaded chunks that are no longer in range */ void UnloadOutOfRangeChunks(void); - // Removes the client from all chunks. Used when switching worlds or destroying the player - void RemoveFromAllChunks(void); - - inline bool IsLoggedIn(void) const { return (m_State >= csAuthenticating); } + inline bool IsLoggedIn(void) const { return (m_State >= eState::csAuthenticating); } /** Called while the client is being ticked from the world via its cPlayer object */ void Tick(float a_Dt); - /** Called while the client is being ticked from the cServer object */ - void ServerTick(float a_Dt); - + /** Mark the clienthandle as disconnected. + Guarantees that no more data is sent or received. Therefore, MUST be called in context of server tick thread. + Will result in cServer releasing the shared_ptr to the handle. + Will eventually result in the object's destruction. */ void Destroy(void); - bool IsPlaying (void) const { return (m_State == csPlaying); } - bool IsDestroyed (void) const { return (m_State == csDestroyed); } - bool IsDestroying(void) const { return (m_State == csDestroying); } + bool IsPlaying (void) const { return (m_State == eState::csPlaying); } + bool IsDestroyed (void) const { return (m_State == eState::csDestroyed); } + bool IsDestroying(void) const { return (m_State == eState::csDestroying); } // The following functions send the various packets: // (Please keep these alpha-sorted) void SendAttachEntity (const cEntity & a_Entity, const cEntity & a_Vehicle); void SendBlockAction (int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType); void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage); - void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); // tolua_export - void SendBlockChanges (int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes); + + /** Sends a singular block change notification to the client. + Callers MUST ensure that the client has been sent or is going to be sent the chunk specified by the arguments. */ + void SendBlockChange(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); + + /** Sends multiple block changes in a single notification to the client. + Callers MUST ensure that the client has been sent or is going to be sent the chunk specified by the arguments. */ + void SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes); + void SendChat (const AString & a_Message, eMessageType a_ChatPrefix, const AString & a_AdditionalData = ""); void SendChat (const cCompositeChat & a_Message); void SendChatAboveActionBar (const AString & a_Message, eMessageType a_ChatPrefix, const AString & a_AdditionalData = ""); void SendChatAboveActionBar (const cCompositeChat & a_Message); void SendChatSystem (const AString & a_Message, eMessageType a_ChatPrefix, const AString & a_AdditionalData = ""); void SendChatSystem (const cCompositeChat & a_Message); - void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer); + void SendChunkData (cWorld & a_ChunkSenderWorld, const cChunkCoords & a_ChunkCoordinates, cChunkDataSerializer & a_Serializer); void SendCollectEntity (const cEntity & a_Entity, const cPlayer & a_Player); void SendDestroyEntity (const cEntity & a_Entity); void SendDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle); @@ -217,7 +239,6 @@ public: // tolua_export void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ); void SendTitleTimes (int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks); void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay, bool a_DoDaylightCycle); // tolua_export - void SendUnloadChunk (int a_ChunkX, int a_ChunkZ); void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity); void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4); void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ); @@ -258,18 +279,12 @@ public: // tolua_export // tolua_end - /** Returns true if the client wants the chunk specified to be sent (in m_ChunksToSend) */ - bool WantsSendChunk(int a_ChunkX, int a_ChunkZ); - - /** Adds the chunk specified to the list of chunks wanted for sending (m_ChunksToSend) */ - void AddWantedChunk(int a_ChunkX, int a_ChunkZ); - // Calls that cProtocol descendants use to report state: void PacketBufferFull(void); void PacketUnknown(UInt32 a_PacketType); void PacketError(UInt32 a_PacketType); - // Calls that cProtocol descendants use for handling packets: + /** Calls that cProtocol descendants use for handling packets. */ void HandleAnimation(int a_Animation); /** Called when the protocol receives a MC|ItemName plugin message, indicating that the player named @@ -319,10 +334,9 @@ public: // tolua_export the NPC UI. */ void HandleNPCTrade(int a_SlotNum); - void HandlePing (void); void HandlePlayerAbilities (bool a_CanFly, bool a_IsFlying, float FlyingSpeed, float WalkingSpeed); void HandlePlayerLook (float a_Rotation, float a_Pitch, bool a_IsOnGround); - void HandlePlayerMoveLook (double a_PosX, double a_PosY, double a_PosZ, double a_Stance, float a_Rotation, float a_Pitch, bool a_IsOnGround); // While m_bPositionConfirmed (normal gameplay) + void HandlePlayerMoveLook (double a_PosX, double a_PosY, double a_PosZ, double a_Stance, float a_Rotation, float a_Pitch, bool a_IsOnGround); /** Verifies and sets player position, performing relevant checks Calls relevant methods to process movement related statistics @@ -361,12 +375,25 @@ public: // tolua_export /** Called by the protocol recognizer when the protocol version is known. */ void SetProtocolVersion(UInt32 a_ProtocolVersion) { m_ProtocolVersion = a_ProtocolVersion; } + /** Called by cServer::Shutdown to indicate m_Player's destruction is out of our control */ + void StopPlayerDestructionManagement() { m_ShouldDestroyPlayer = false; } + /** Returns the protocol version number of the protocol that the client is talking. Returns zero if the protocol version is not (yet) known. */ UInt32 GetProtocolVersion(void) const { return m_ProtocolVersion; } // tolua_export - void InvalidateCachedSentChunk(); + /** Add a new functor which will commit processed incoming network data in the world tick thread */ + template <typename FunctionType, typename... FunctionArguments> + void QueueDataCommit(FunctionType && a_Function, FunctionArguments && ... a_FunctionArguments) + { + m_DataCommitQueue.emplace_back(std::bind(std::forward<FunctionType>(a_Function), std::forward<FunctionArguments>(a_FunctionArguments)...)); + } + + /** Enforces a rate limit on block interactions. + Will kick client if limit exceeded. + Returns true if the block interactions rate is within MAX_BLOCK_CHANGE_INTERACTIONS per tick. */ + bool EnforceBlockInteractionsRate(void); - bool IsPlayerChunkSent(); + cProtocol & GetProtocol(void) { return m_Protocol; } private: @@ -385,15 +412,35 @@ private: AString m_IPString; AString m_Username; - AString m_Password; Json::Value m_Properties; - cCriticalSection m_CSChunkLists; - std::unordered_set<cChunkCoords, cChunkCoordsHash> m_LoadedChunks; // Chunks that the player belongs to - std::unordered_set<cChunkCoords, cChunkCoordsHash> m_ChunksToSend; // Chunks that need to be sent to the player (queued because they weren't generated yet or there's not enough time to send them) - cChunkCoordsList m_SentChunks; // Chunks that are currently sent to the client + /** Flag indicating whether it is safe to access m_Player in our destructor to destroy it + While used inter-thread, atomicity not necessary as is only ever set in the main thread and read in a destructor, which is not called as long as someone has a reference to us */ + bool m_ShouldDestroyPlayer; + + struct sChunkCoordsHash + { + size_t operator()(const cChunkCoords & a_ChunkCoords) const + { + size_t Seed = static_cast<size_t>(a_ChunkCoords.m_ChunkX) + 0x9e3779b9; + return (Seed ^= static_cast<size_t>(a_ChunkCoords.m_ChunkZ) + 0x9e3779b9 + (Seed << 6) + (Seed >> 2)); + } + }; + + /** Chunks that are currently loaded by the player */ + std::unordered_map<cChunkCoords, std::reference_wrapper<cWorld>, cChunkCoordsHash> m_LoadedChunks; - cProtocol * m_Protocol; + /** Functors which will commit processed incoming network data to the world. + Functors MUST be called in the context of the world's tick thread */ + std::vector<std::function<void()>> m_DataCommitQueue; + + cProtocolRecognizer m_Protocol; + + /** Empties the incoming send queue in a thread safe manner and processes the data. */ + void ProcessQueuedIncomingData(void); + + /** Empties the data commit queue and queues the data to be executed in the context of the world tick thread. */ + void ProcessDataCommitQueue(void); /** Protects m_IncomingData against multithreaded access. */ cCriticalSection m_CSIncomingData; @@ -402,30 +449,23 @@ private: Protected by m_CSIncomingData. */ AString m_IncomingData; + /** Empties the outgoing send queue in a thread safe manner and sends the data via the network link. */ + void ProcessQueuedOutgoingData(void); + /** Protects m_OutgoingData against multithreaded access. */ cCriticalSection m_CSOutgoingData; - /** Buffer for storing outgoing data from any thread; will get sent in Tick() (to prevent deadlocks). + /** Queue for the outgoing data to be sent through the link until it is processed in Tick(). Protected by m_CSOutgoingData. */ AString m_OutgoingData; - Vector3d m_ConfirmPosition; + /** Protects m_Link against multithreaded access. */ + cCriticalSection m_CSLink; + /** Non-owning pointer to the player object we are associated with + It is guaranteed to exist whilst the server is running and this clienthandle is alive */ cPlayer * m_Player; - /** This is an optimization which saves you an iteration of m_SentChunks if you just want to know - whether or not the player is standing at a sent chunk. - If this is equal to the coordinates of the chunk the player is currrently standing at, then this must be a sent chunk - and a member of m_SentChunks. - Otherwise, this contains an arbitrary value which should not be used. */ - cChunkCoords m_CachedSentChunk; - - bool m_HasSentDC; ///< True if a Disconnect packet has been sent in either direction - - // Chunk position when the last StreamChunks() was called; used to avoid re-streaming while in the same chunk - int m_LastStreamedChunkX; - int m_LastStreamedChunkZ; - /** Number of ticks since the last network packet was received (increased in Tick(), reset in OnReceivedData()) */ std::atomic<int> m_TicksSinceLastPacket; @@ -451,27 +491,11 @@ private: int m_LastDigBlockY; int m_LastDigBlockZ; - enum eState - { - csConnected, ///< The client has just connected, waiting for their handshake / login - csAuthenticating, ///< The client has logged in, waiting for external authentication - csAuthenticated, ///< The client has been authenticated, will start streaming chunks in the next tick - csDownloadingWorld, ///< The client is waiting for chunks, we're waiting for the loader to provide and send them - csConfirmingPos, ///< The client has been sent the position packet, waiting for them to repeat the position back - csPlaying, ///< Normal gameplay - csDestroying, ///< The client is being destroyed, don't queue any more packets / don't add to chunks - csDestroyed, ///< The client has been destroyed, the destructor is to be called from the owner thread - - // TODO: Add Kicking here as well - } ; - std::atomic<eState> m_State; - /** m_State needs to be locked in the Destroy() function so that the destruction code doesn't run twice on two different threads */ - cCriticalSection m_CSDestroyingState; - - /** If set to true during csDownloadingWorld, the tick thread calls CheckIfWorldDownloaded() */ - bool m_ShouldCheckDownloaded; + /** A flag to indicate whether a player has traversed chunks and should therefore have their view resent. + The flag is set in position updates, and causes StreamAllChunks to be called in the next server tick. */ + bool m_ShouldRefreshSentChunks; /** Number of explosions sent this tick */ int m_NumExplosionsThisTick; @@ -487,9 +511,6 @@ private: /** Contains the UUID used by Mojang to identify the player's account. Short UUID stored here (without dashes) */ AString m_UUID; - /** Set to true when the chunk where the player is is sent to the client. Used for spawning the player */ - bool m_HasSentPlayerChunk; - /** Client Settings */ AString m_Locale; @@ -509,16 +530,7 @@ private: m_CSOutgoingData is used to synchronize access for sending data. */ cTCPLinkPtr m_Link; - /** Shared pointer to self, so that this instance can keep itself alive when needed. */ - cClientHandlePtr m_Self; - - - /** Returns true if the rate block interactions is within a reasonable limit (bot protection) */ - bool CheckBlockInteractionsRate(void); - - /** Adds a single chunk to be streamed to the client; used by StreamChunks() */ - void StreamChunk(int a_ChunkX, int a_ChunkZ, cChunkSender::eChunkPriority a_Priority); - + /** Handles the DIG_STARTED dig packet: */ void HandleBlockDigStarted (int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, BLOCKTYPE a_OldBlock, NIBBLETYPE a_OldMeta); @@ -540,9 +552,6 @@ private: /** Called when the network socket has been closed. */ void SocketClosed(void); - /** Called right after the instance is created to store its SharedPtr inside. */ - void SetSelf(cClientHandlePtr a_Self); - // cTCPLink::cCallbacks overrides: virtual void OnLinkCreated(cTCPLinkPtr a_Link) override; virtual void OnReceivedData(const char * a_Data, size_t a_Length) override; diff --git a/src/CompositeChat.cpp b/src/CompositeChat.cpp index 2a112f810..3b93db2cb 100644 --- a/src/CompositeChat.cpp +++ b/src/CompositeChat.cpp @@ -23,8 +23,9 @@ cCompositeChat::cCompositeChat(void) : -cCompositeChat::cCompositeChat(const AString & a_ParseText, eMessageType a_MessageType) : - m_MessageType(a_MessageType) +cCompositeChat::cCompositeChat(const AString & a_ParseText, eMessageType a_MessageType, const AString & a_AdditionalMessageTypeData) : + m_MessageType(a_MessageType), + m_AdditionalMessageTypeData(a_AdditionalMessageTypeData) { ParseText(a_ParseText); } diff --git a/src/CompositeChat.h b/src/CompositeChat.h index 2c1ff5fab..bdf725120 100644 --- a/src/CompositeChat.h +++ b/src/CompositeChat.h @@ -130,7 +130,7 @@ public: /** Creates a new chat message and parses the text into parts. Recognizes "http:" and "https:" links and @color-codes. Uses ParseText() for the actual parsing. */ - cCompositeChat(const AString & a_ParseText, eMessageType a_MessageType = mtCustom); + cCompositeChat(const AString & a_ParseText, eMessageType a_MessageType = mtCustom, const AString & a_AdditionalMessageTypeData = ""); ~cCompositeChat(); diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 2adbc3142..8d74ee99a 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -41,7 +41,6 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d m_LastPosition(a_X, a_Y, a_Z), m_EntityType(a_EntityType), m_World(nullptr), - m_IsWorldChangeScheduled(false), m_IsFireproof(false), m_TicksSinceLastBurnDamage(0), m_TicksSinceLastLavaDamage(0), @@ -52,6 +51,7 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d m_IsSubmerged(false), m_AirLevel(0), m_AirTickTimer(0), + m_PortalCooldownData({0, false, true}), m_TicksAlive(0), m_IsTicking(false), m_ParentChunk(nullptr), @@ -77,9 +77,6 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d cEntity::~cEntity() { - - // Before deleting, the entity needs to have been removed from the world, if ever added - ASSERT((m_World == nullptr) || !m_World->HasEntity(m_UniqueID)); ASSERT(!IsTicking()); /* @@ -201,7 +198,7 @@ void cEntity::SetParentChunk(cChunk * a_Chunk) -cChunk * cEntity::GetParentChunk() +cChunk * cEntity::GetParentChunk() const { return m_ParentChunk; } @@ -1369,36 +1366,13 @@ void cEntity::DetectCacti(void) -void cEntity::ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_SetPortalCooldown) -{ - m_NewWorld = a_World; - m_NewWorldPosition = a_NewPosition; - m_IsWorldChangeScheduled = true; - m_WorldChangeSetPortalCooldown = a_SetPortalCooldown; -} - - - - bool cEntity::DetectPortal() { - // If somebody scheduled a world change with ScheduleMoveToWorld, change worlds now. - if (m_IsWorldChangeScheduled) + if (!m_PortalCooldownData.m_PositionValid) { - m_IsWorldChangeScheduled = false; - - if (m_WorldChangeSetPortalCooldown) - { - // Delay the portal check. - m_PortalCooldownData.m_TicksDelayed = 0; - m_PortalCooldownData.m_ShouldPreventTeleportation = true; - } - - MoveToWorld(m_NewWorld, false, m_NewWorldPosition); - return true; + return false; } - - if (GetWorld()->GetDimension() == dimOverworld) + else if (GetWorld()->GetDimension() == dimOverworld) { if (GetWorld()->GetLinkedNetherWorldName().empty() && GetWorld()->GetLinkedEndWorldName().empty()) { @@ -1412,7 +1386,7 @@ bool cEntity::DetectPortal() return false; } - int X = POSX_TOINT, Y = POSY_TOINT, Z = POSZ_TOINT; + const int X = POSX_TOINT, Y = POSY_TOINT, Z = POSZ_TOINT; if ((Y > 0) && (Y < cChunkDef::Height)) { switch (GetWorld()->GetBlock(X, Y, Z)) @@ -1425,62 +1399,56 @@ bool cEntity::DetectPortal() return false; } - if (IsPlayer() && !(reinterpret_cast<cPlayer *>(this))->IsGameModeCreative() && (m_PortalCooldownData.m_TicksDelayed != 80)) + if ( + IsPlayer() && + // !reinterpret_cast<cPlayer *>(this)->IsGameModeCreative() && // TODO: fix portal travel prevention - client sends outdated position data throwing off checks + (m_PortalCooldownData.m_TicksDelayed != 80) + ) { // Delay teleportation for four seconds if the entity is a non-creative player m_PortalCooldownData.m_TicksDelayed++; return false; } - m_PortalCooldownData.m_TicksDelayed = 0; + + m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn + m_PortalCooldownData.m_PositionValid = false; if (GetWorld()->GetDimension() == dimNether) { - if (GetWorld()->GetLinkedOverworldName().empty()) - { - return false; - } - - m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn + cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName()); + ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() - if (IsPlayer()) + if (GetWorld()->GetLinkedOverworldName().empty() || !OnPreWorldTravel(*TargetWorld)) { - // Send a respawn packet before world is loaded / generated so the client isn't left in limbo - (reinterpret_cast<cPlayer *>(this))->GetClientHandle()->SendRespawn(dimOverworld); + return false; } Vector3d TargetPos = GetPosition(); TargetPos.x *= 8.0; TargetPos.z *= 8.0; - cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName()); - ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() LOGD("Jumping nether -> overworld"); - new cNetherPortalScanner(this, TargetWorld, TargetPos, 256); + new cNetherPortalScanner(GetUniqueID(), GetWorld()->GetDimension(), TargetWorld, TargetPos, 256); + return true; } else { - if (GetWorld()->GetLinkedNetherWorldName().empty()) - { - return false; - } - - m_PortalCooldownData.m_ShouldPreventTeleportation = true; + cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName()); + ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() - if (IsPlayer()) + if (GetWorld()->GetLinkedNetherWorldName().empty() || !OnPreWorldTravel(*TargetWorld)) { - reinterpret_cast<cPlayer *>(this)->AwardAchievement(achEnterPortal); - reinterpret_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(dimNether); + return false; } Vector3d TargetPos = GetPosition(); TargetPos.x /= 8.0; TargetPos.z /= 8.0; - cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName()); - ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() LOGD("Jumping overworld -> nether"); - new cNetherPortalScanner(this, TargetWorld, TargetPos, 128); + new cNetherPortalScanner(GetUniqueID(), GetWorld()->GetDimension(), TargetWorld, TargetPos, 128); + return true; } } @@ -1491,6 +1459,9 @@ bool cEntity::DetectPortal() return false; } + m_PortalCooldownData.m_ShouldPreventTeleportation = true; + m_PortalCooldownData.m_PositionValid = false; + if (GetWorld()->GetDimension() == dimEnd) { @@ -1499,18 +1470,10 @@ bool cEntity::DetectPortal() return false; } - m_PortalCooldownData.m_ShouldPreventTeleportation = true; - - if (IsPlayer()) - { - cPlayer * Player = reinterpret_cast<cPlayer *>(this); - Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z); - Player->GetClientHandle()->SendRespawn(dimOverworld); - } - cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName()); ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() - return MoveToWorld(TargetWorld, false); + + return MoveToWorld(*TargetWorld, Vector3d(TargetWorld->GetSpawnX(), TargetWorld->GetSpawnY(), TargetWorld->GetSpawnZ())); } else { @@ -1519,27 +1482,18 @@ bool cEntity::DetectPortal() return false; } - m_PortalCooldownData.m_ShouldPreventTeleportation = true; - - if (IsPlayer()) - { - reinterpret_cast<cPlayer *>(this)->AwardAchievement(achEnterTheEnd); - reinterpret_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(dimEnd); - } - cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName()); ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() - return MoveToWorld(TargetWorld, false); - } + return MoveToWorld(*TargetWorld, GetPosition()); + } } default: break; } } // Allow portals to work again - m_PortalCooldownData.m_ShouldPreventTeleportation = false; - m_PortalCooldownData.m_TicksDelayed = 0; + m_PortalCooldownData = { 0, false, true }; return false; } @@ -1547,99 +1501,6 @@ bool cEntity::DetectPortal() -bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) -{ - UNUSED(a_ShouldSendRespawn); - ASSERT(a_World != nullptr); - ASSERT(IsTicking()); - - if (GetWorld() == a_World) - { - // Don't move to same world - return false; - } - - // Ask the plugins if the entity is allowed to changing the world - if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World)) - { - // A Plugin doesn't allow the entity to changing the world - return false; - } - - // Stop ticking, in preperation for detaching from this world. - SetIsTicking(false); - - // Tell others we are gone - GetWorld()->BroadcastDestroyEntity(*this); - - // Set position to the new position - SetPosition(a_NewPosition); - - // 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(); - cChunk * ParentChunk = GetParentChunk(); - SetWorld(a_World); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value - OldWorld->QueueTask([this, ParentChunk, 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(), - ParentChunk->GetPosX(), ParentChunk->GetPosZ() - ); - ParentChunk->RemoveEntity(this); - a_World->AddEntity(this); - cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*this, a_OldWorld); - }); - return true; -} - - - - - -bool cEntity::MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) -{ - return DoMoveToWorld(a_World, a_ShouldSendRespawn, a_NewPosition); -} - - - - - -bool cEntity::MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn) -{ - return MoveToWorld(a_World, a_ShouldSendRespawn, Vector3d(a_World->GetSpawnX(), a_World->GetSpawnY(), a_World->GetSpawnZ())); -} - - - - - -bool cEntity::MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn) -{ - cWorld * World = cRoot::Get()->GetWorld(a_WorldName); - if (World == nullptr) - { - LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName.c_str()); - return false; - } - - return DoMoveToWorld(World, a_ShouldSendRespawn, Vector3d(World->GetSpawnX(), World->GetSpawnY(), World->GetSpawnZ())); -} - - - - - void cEntity::SetSwimState(cChunk & a_Chunk) { int RelY = FloorC(GetPosY() + 0.1); @@ -1836,19 +1697,19 @@ void cEntity::StopBurning(void) void cEntity::TeleportToEntity(cEntity & a_Entity) { - TeleportToCoords(a_Entity.GetPosX(), a_Entity.GetPosY(), a_Entity.GetPosZ()); + TeleportToCoords(a_Entity.GetPosition()); } -void cEntity::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) +void cEntity::TeleportToCoords(const Vector3d & a_Position) { - // ask the plugins to allow teleport to the new position. - if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, Vector3d(a_PosX, a_PosY, a_PosZ))) + // Ask the plugins to allow teleport to the new position. + if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, a_Position)) { - SetPosition(a_PosX, a_PosY, a_PosZ); + SetPosition(a_Position); m_World->BroadcastTeleportEntity(*this); } } @@ -1857,6 +1718,91 @@ void cEntity::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) +bool cEntity::MoveToWorld(cWorld & a_NewWorld, const Vector3d & a_NewPosition) +{ + auto PreviousDimension = GetWorld()->GetDimension(); + if (OnPreWorldTravel(a_NewWorld)) + { + OnPostWorldTravel(PreviousDimension, a_NewPosition); + SetPosition(a_NewPosition); // Just in case :) + return true; + } + return false; +} + + + + + +bool cEntity::MoveToWorld(const AString & a_WorldName, const Vector3d & a_NewPosition) +{ + auto World = cRoot::Get()->GetWorld(a_WorldName); + if (World == nullptr) + { + LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName.c_str()); + return false; + } + + return MoveToWorld(*World, a_NewPosition); +} + + + + + +bool cEntity::OnPreWorldTravel(cWorld & a_NewWorld) +{ + ASSERT(IsTicking()); + + if (GetWorld() == &a_NewWorld) + { + // Don't move to same world + return false; + } + + // Ask the plugins if the player is allowed to changing the world + if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, a_NewWorld)) + { + // A Plugin doesn't allow the player to changing the world + return false; + } + + // Prevent further ticking in this world + SetIsTicking(false); + + auto Entity = GetParentChunk()->AcquireAssociatedEntityPtr(*this); + + // Broadcast for other people that the player is gone. + GetWorld()->BroadcastDestroyEntity(*this); + + cpp14::move_on_copy_wrapper<decltype(Entity)> EntityPtr(std::move(Entity)); + a_NewWorld.QueueTask( + [EntityPtr](cWorld & a_DestinationWorld) mutable + { + // Entity changed world, call the hook + cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*EntityPtr.value, *EntityPtr.value->GetWorld()); + + EntityPtr.value->Initialize(std::move(EntityPtr.value), a_DestinationWorld); + } + ); + + return true; +} + + + + + +void cEntity::OnPostWorldTravel(eDimension a_PreviousDimension, const Vector3d & a_RecommendedPosition) +{ + SetPosition(a_RecommendedPosition); + m_PortalCooldownData.m_PositionValid = true; +} + + + + + void cEntity::BroadcastMovementUpdate(const cClientHandle * a_Exclude) { // Process packet sending every two ticks diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index c543fd9c1..25f6e76bf 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -215,7 +215,7 @@ public: void SetPosY (double a_PosY) { SetPosition({m_Position.x, a_PosY, m_Position.z}); } void SetPosZ (double a_PosZ) { SetPosition({m_Position.x, m_Position.y, a_PosZ}); } void SetPosition(double a_PosX, double a_PosY, double a_PosZ) { SetPosition({a_PosX, a_PosY, a_PosZ}); } - void SetPosition(const Vector3d & a_Position); + virtual void SetPosition(const Vector3d & a_Position); void SetYaw (double a_Yaw); // In degrees, normalizes to [-180, +180) void SetPitch (double a_Pitch); // In degrees, normalizes to [-180, +180) void SetRoll (double a_Roll); // In degrees, normalizes to [-180, +180) @@ -300,6 +300,9 @@ public: If it returns false, the entity hasn't receive any damage. */ virtual bool DoTakeDamage(TakeDamageInfo & a_TDI); + /** Returns the position stored before the last call to SetPosition. */ + Vector3d GetLastPosition(void) const { return m_LastPosition; } + // tolua_begin /** Returns the hitpoints that this pawn can deal to a_Receiver using its equipped items */ @@ -405,22 +408,29 @@ public: virtual void TeleportToEntity(cEntity & a_Entity); /** Teleports to the coordinates specified */ - 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 MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition); + virtual void TeleportToCoords(const Vector3d & a_Position); - /** Moves entity to specified world, taking a world pointer */ - bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn = true); + /** Moves entity to the specified world reference and to the specified position. + Returns if the entity changed worlds. */ + bool MoveToWorld(cWorld & a_NewWorld, const Vector3d & a_NewPosition); - /** Moves entity to specified world, taking a world name */ - bool MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn = true); + /** Moves entity to the specified world name and to the specified position. + Returns if the entity changed worlds. */ + bool MoveToWorld(const AString & a_WorldName, const Vector3d & a_NewPosition); // tolua_end - virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition); + /** Event called on initial world change request. + The recommended coordinates have not been calculated, and the destination spawn area may not have been prepared yet. + Descendants MAY overload to modify the specifics of their world change behaviour. */ + virtual bool OnPreWorldTravel(cWorld & a_NewWorld); + + /** Event called after world change request has been fully processed. + The suggested spawn coordinates have been calculated. + Descendants MAY ignore the recommended position. + This function MUST be called within the context of the destination world's tick thread: + we may safely ignore the originating world as the entity is guaranteed to no longer be accessed / ticked from that thread. */ + virtual void OnPostWorldTravel(eDimension a_PreviousDimension, const Vector3d & a_RecommendedPosition); /** Updates clients of changes in the entity. */ virtual void BroadcastMovementUpdate(const cClientHandle * a_Exclude = nullptr); @@ -490,14 +500,17 @@ public: } /** Sets the internal world pointer to a new cWorld, doesn't update anything else. */ - void SetWorld(cWorld * a_World) { m_World = a_World; } + void SetWorld(cWorld * a_World) + { + m_World = a_World; + } /** Sets the parent chunk, which is the chunk responsible for ticking this entity. Only cChunk::AddEntity and cChunk::RemoveEntity cChunk::~cChunk should ever call this. */ void SetParentChunk(cChunk * a_Chunk); /** Returns the chunk responsible for ticking this entity. */ - cChunk * GetParentChunk(); + cChunk * GetParentChunk() const; /** Set the entity's status to either ticking or not ticking. */ void SetIsTicking(bool a_IsTicking); @@ -512,6 +525,10 @@ protected: /** Whether the entity has just exited the portal, and should therefore not be teleported again. This prevents teleportation loops, and is reset when the entity has moved out of the portal. */ bool m_ShouldPreventTeleportation; + + /** Whether the entity's position has been calculated and set. + Processing does not occur until this is true. */ + bool m_PositionValid; }; static cCriticalSection m_CSCount; @@ -563,12 +580,6 @@ protected: cWorld * m_World; - /** State variables for ScheduleMoveToWorld. */ - bool m_IsWorldChangeScheduled; - bool m_WorldChangeSetPortalCooldown; - cWorld * m_NewWorld; - Vector3d m_NewWorldPosition; - /** Whether the entity is capable of taking fire or lava damage. */ bool m_IsFireproof; @@ -656,8 +667,6 @@ private: int m_InvulnerableTicks; } ; // tolua_export -typedef std::list<cEntity *> cEntityList; - diff --git a/src/Entities/Pawn.cpp b/src/Entities/Pawn.cpp index 04425dd51..c83e7f3cb 100644 --- a/src/Entities/Pawn.cpp +++ b/src/Entities/Pawn.cpp @@ -28,8 +28,10 @@ cPawn::cPawn(eEntityType a_EntityType, double a_Width, double a_Height) : cPawn::~cPawn() +bool cPawn::OnPreWorldTravel(cWorld & a_NewWorld) { ASSERT(m_TargetingMe.size() == 0); + return super::OnPreWorldTravel(a_NewWorld); } diff --git a/src/Entities/Pawn.h b/src/Entities/Pawn.h index 6a7035ee6..74bccc4a3 100644 --- a/src/Entities/Pawn.h +++ b/src/Entities/Pawn.h @@ -23,6 +23,8 @@ public: cPawn(eEntityType a_EntityType, double a_Width, double a_Height); ~cPawn(); + + virtual bool OnPreWorldTravel(cWorld & a_NewWorld) override; virtual void Destroyed() override; virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index e4c0fdaa5..babe31978 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -36,6 +36,14 @@ // 1000 = once per second #define PLAYER_LIST_TIME_MS std::chrono::milliseconds(1000) +#define DO_WITH_VALID_CLIENTHANDLE(Command) \ + auto ClientHandle = m_ClientHandle.lock(); \ + if (ClientHandle != nullptr) \ + { \ + ClientHandle->Command \ + } + + @@ -50,7 +58,7 @@ const int cPlayer::EATING_TICKS = 30; -cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) : +cPlayer::cPlayer(std::weak_ptr<cClientHandle> a_Client, const AString & a_PlayerName) : super(etPlayer, 0.6, 1.8), m_bVisible(true), m_FoodLevel(MAX_FOOD_LEVEL), @@ -85,8 +93,8 @@ cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) : m_FloaterID(cEntity::INVALID_ID), m_Team(nullptr), m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL), - m_bIsTeleporting(false), - m_UUID((a_Client != nullptr) ? a_Client->GetUUID() : ""), + m_IsChangingWorlds(false), + m_UUID(a_Client.lock()->GetUUID()), m_CustomName("") { ASSERT(a_PlayerName.length() <= 16); // Otherwise this player could crash many clients... @@ -148,25 +156,6 @@ cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) : -bool cPlayer::Initialize(cWorld & a_World) -{ - UNUSED(a_World); - ASSERT(GetWorld() != nullptr); - ASSERT(GetParentChunk() == nullptr); - GetWorld()->AddPlayer(this); - - cPluginManager::Get()->CallHookSpawnedEntity(*GetWorld(), *this); - - // Spawn the entity on the clients: - GetWorld()->BroadcastSpawnEntity(*this); - - return true; -} - - - - - cPlayer::~cPlayer(void) { if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*this)) @@ -180,10 +169,6 @@ cPlayer::~cPlayer(void) // Notify the server that the player is being destroyed cRoot::Get()->GetServer()->PlayerDestroying(this); - SaveToDisk(); - - m_ClientHandle = nullptr; - delete m_InventoryWindow; m_InventoryWindow = nullptr; @@ -196,6 +181,7 @@ cPlayer::~cPlayer(void) void cPlayer::Destroyed() { + SaveToDisk(); CloseWindow(false); super::Destroyed(); } @@ -206,7 +192,7 @@ void cPlayer::Destroyed() void cPlayer::SpawnOn(cClientHandle & a_Client) { - if (!m_bVisible || (m_ClientHandle.get() == (&a_Client))) + if (!m_bVisible || (m_ClientHandle.lock().get() == &a_Client)) { return; } @@ -225,28 +211,22 @@ void cPlayer::SpawnOn(cClientHandle & a_Client) void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { - if (m_ClientHandle != nullptr) + auto ClientHandle = m_ClientHandle.lock(); + if ((ClientHandle == nullptr) || ClientHandle->IsDestroyed()) { - if (m_ClientHandle->IsDestroyed()) - { - // This should not happen, because destroying a client will remove it from the world, but just in case - ASSERT(!"Player ticked whilst in the process of destruction!"); - m_ClientHandle = nullptr; - return; - } + // If the ClientHandle was deleted or destroyed, the client has disconnected + // In theory, if it was only destroyed, we may continue ticking (just nothing would be sent over the network) + // For performance however, simply bail out - if (!m_ClientHandle->IsPlaying()) - { - // We're not yet in the game, ignore everything - return; - } + return; } - else + + if (!ClientHandle->IsPlaying()) { - ASSERT(!"Player ticked whilst in the process of destruction!"); + // We're not yet in the game, ignore everything + return; } - m_Stats.AddValue(statMinutesPlayed, 1); // Handle a frozen player @@ -255,9 +235,8 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { return; } - ASSERT((GetParentChunk() != nullptr) && (GetParentChunk()->IsValid())); - ASSERT(a_Chunk.IsValid()); + ASSERT(GetParentChunk() != nullptr); super::Tick(a_Dt, a_Chunk); @@ -273,7 +252,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) SendExperience(); } - BroadcastMovementUpdate(m_ClientHandle.get()); + BroadcastMovementUpdate(ClientHandle.get()); if (m_Health > 0) // make sure player is alive { @@ -319,55 +298,16 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cPlayer::TickFreezeCode() { - if (m_IsFrozen) + if (m_IsFrozen && (GetWorld()->GetWorldAge() % 100 == 0)) { - if ((!m_IsManuallyFrozen) && (GetClientHandle()->IsPlayerChunkSent())) - { - // If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded and sent - Unfreeze(); + // Despite the client side freeze, the player may be able to move a little by + // Jumping or canceling flight. Re-freeze every now and then + Freeze(GetPosition()); - // Pull the player out of any solids that might have loaded on them. - PREPARE_REL_AND_CHUNK(GetPosition(), *(GetParentChunk())); - if (RelSuccess) - { - int NewY = Rel.y; - if (NewY < 0) - { - NewY = 0; - } - while (NewY < cChunkDef::Height - 2) - { - // If we find a position with enough space for the player - if ( - (Chunk->GetBlock(Rel.x, NewY, Rel.z) == E_BLOCK_AIR) && - (Chunk->GetBlock(Rel.x, NewY + 1, Rel.z) == E_BLOCK_AIR) - ) - { - // If the found position is not the same as the original - if (NewY != Rel.y) - { - SetPosition(GetPosition().x, NewY, GetPosition().z); - GetClientHandle()->SendPlayerPosition(); - } - break; - } - ++NewY; - } - } - } - else if (GetWorld()->GetWorldAge() % 100 == 0) - { - // Despite the client side freeze, the player may be able to move a little by - // Jumping or canceling flight. Re-freeze every now and then - FreezeInternal(GetPosition(), m_IsManuallyFrozen); - } - } - else - { - if (!GetClientHandle()->IsPlayerChunkSent() || (!GetParentChunk()->IsValid())) - { - FreezeInternal(GetPosition(), false); - } + DO_WITH_VALID_CLIENTHANDLE( + SendPlayerPosition(); + BroadcastMovementUpdate(ClientHandle.get()); + ) } } @@ -499,7 +439,7 @@ void cPlayer::StartChargingBow(void) LOGD("Player \"%s\" started charging their bow", GetName().c_str()); m_IsChargingBow = true; m_BowCharge = 0; - m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get()); + m_World->BroadcastEntityMetadata(*this, m_ClientHandle.lock().get()); } @@ -512,7 +452,7 @@ int cPlayer::FinishChargingBow(void) int res = m_BowCharge; m_IsChargingBow = false; m_BowCharge = 0; - m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get()); + m_World->BroadcastEntityMetadata(*this, m_ClientHandle.lock().get()); return res; } @@ -526,7 +466,7 @@ void cPlayer::CancelChargingBow(void) LOGD("Player \"%s\" cancelled charging their bow at a charge of %d", GetName().c_str(), m_BowCharge); m_IsChargingBow = false; m_BowCharge = 0; - m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get()); + m_World->BroadcastEntityMetadata(*this, m_ClientHandle.lock().get()); } @@ -654,7 +594,11 @@ void cPlayer::FinishEating(void) m_EatingFinishTick = -1; // Send the packets: - m_ClientHandle->SendEntityStatus(*this, esPlayerEatingAccepted); + auto ClientHandle = m_ClientHandle.lock(); + if (ClientHandle == nullptr) + { + ClientHandle->SendEntityStatus(*this, esPlayerEatingAccepted); + } m_World->BroadcastEntityMetadata(*this); // consume the item: @@ -684,10 +628,9 @@ void cPlayer::AbortEating(void) void cPlayer::SendHealth(void) { - if (m_ClientHandle != nullptr) - { - m_ClientHandle->SendHealth(); - } + DO_WITH_VALID_CLIENTHANDLE( + SendHealth(); + ) } @@ -696,11 +639,10 @@ void cPlayer::SendHealth(void) void cPlayer::SendExperience(void) { - if (m_ClientHandle != nullptr) - { - m_ClientHandle->SendExperience(); + DO_WITH_VALID_CLIENTHANDLE( + SendExperience(); m_bDirtyExperience = false; - } + ) } @@ -762,8 +704,9 @@ void cPlayer::SetNormalMaxSpeed(double a_Speed) m_NormalMaxSpeed = a_Speed; if (!m_IsSprinting && !m_IsFlying && !m_IsFrozen) { - // If we are frozen, we do not send this yet. We send when unfreeze() is called - m_ClientHandle->SendPlayerMaxSpeed(); + DO_WITH_VALID_CLIENTHANDLE( + SendPlayerMaxSpeed(); + ) } } @@ -776,8 +719,9 @@ void cPlayer::SetSprintingMaxSpeed(double a_Speed) m_SprintingMaxSpeed = a_Speed; if (m_IsSprinting && !m_IsFlying && !m_IsFrozen) { - // If we are frozen, we do not send this yet. We send when unfreeze() is called - m_ClientHandle->SendPlayerMaxSpeed(); + DO_WITH_VALID_CLIENTHANDLE( + SendPlayerMaxSpeed(); + ) } } @@ -789,11 +733,13 @@ void cPlayer::SetFlyingMaxSpeed(double a_Speed) { m_FlyingMaxSpeed = a_Speed; - // Update the flying speed, always: + // Update the flying speed: if (!m_IsFrozen) { // If we are frozen, we do not send this yet. We send when unfreeze() is called - m_ClientHandle->SendPlayerAbilities(); + DO_WITH_VALID_CLIENTHANDLE( + SendPlayerAbilities(); + ) } } @@ -827,7 +773,9 @@ void cPlayer::SetSprint(bool a_IsSprinting) } m_IsSprinting = a_IsSprinting; - m_ClientHandle->SendPlayerMaxSpeed(); + DO_WITH_VALID_CLIENTHANDLE( + SendPlayerMaxSpeed(); + ) } @@ -842,7 +790,9 @@ void cPlayer::SetCanFly(bool a_CanFly) } m_CanFly = a_CanFly; - m_ClientHandle->SendPlayerAbilities(); + DO_WITH_VALID_CLIENTHANDLE( + SendPlayerAbilities(); + ) } @@ -904,7 +854,9 @@ void cPlayer::SetFlying(bool a_IsFlying) if (!m_IsFrozen) { // If we are frozen, we do not send this yet. We send when unfreeze() is called - m_ClientHandle->SendPlayerAbilities(); + DO_WITH_VALID_CLIENTHANDLE( + SendPlayerAbilities(); + ) } } @@ -1147,18 +1099,20 @@ void cPlayer::Respawn(void) m_LifetimeTotalXp = 0; // ToDo: send score to client? How? - m_ClientHandle->SendRespawn(m_SpawnWorld->GetDimension(), true); + DO_WITH_VALID_CLIENTHANDLE( + SendRespawn(m_SpawnWorld->GetDimension(), true); + ) // Extinguish the fire: StopBurning(); if (GetWorld() != m_SpawnWorld) { - MoveToWorld(m_SpawnWorld, false, GetLastBedPos()); + MoveToWorld(*m_SpawnWorld, GetLastBedPos()); } else { - TeleportToCoords(GetLastBedPos().x, GetLastBedPos().y, GetLastBedPos().z); + TeleportToCoords(GetLastBedPos()); } SetVisible(true); @@ -1335,7 +1289,9 @@ void cPlayer::SetGameMode(eGameMode a_GameMode) } m_GameMode = a_GameMode; - m_ClientHandle->SendGameMode(a_GameMode); + DO_WITH_VALID_CLIENTHANDLE( + SendGameMode(a_GameMode); + ) SetCapabilities(); @@ -1422,7 +1378,9 @@ unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach) StatValue New = m_Stats.AddValue(a_Ach); // Achievement Get! - m_ClientHandle->SendStatistics(m_Stats); + DO_WITH_VALID_CLIENTHANDLE( + SendStatistics(m_Stats); + ) return static_cast<unsigned int>(New); } @@ -1432,18 +1390,18 @@ unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach) -void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) +void cPlayer::TeleportToCoords(const Vector3d & a_Position) { - // ask plugins to allow teleport to the new position. - if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, Vector3d(a_PosX, a_PosY, a_PosZ))) + // Ask plugins to allow teleport to the new position. + if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, a_Position)) { - SetPosition(a_PosX, a_PosY, a_PosZ); - FreezeInternal(GetPosition(), false); - m_LastGroundHeight = a_PosY; - m_bIsTeleporting = true; + SetPosition(a_Position); + m_LastGroundHeight = a_Position.y; m_World->BroadcastTeleportEntity(*this, GetClientHandle()); - m_ClientHandle->SendPlayerMoveLook(); + DO_WITH_VALID_CLIENTHANDLE( + SendPlayerMoveLook(); + ) } } @@ -1453,7 +1411,32 @@ void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) void cPlayer::Freeze(const Vector3d & a_Location) { - FreezeInternal(a_Location, true); + SetSpeed(0, 0, 0); + SetPosition(a_Location); + m_IsFrozen = true; + + double NormalMaxSpeed = GetNormalMaxSpeed(); + double SprintMaxSpeed = GetSprintingMaxSpeed(); + double FlyingMaxpeed = GetFlyingMaxSpeed(); + bool IsFlying = m_IsFlying; + + // Set the client-side speed to 0 + m_NormalMaxSpeed = 0; + m_SprintingMaxSpeed = 0; + m_FlyingMaxSpeed = 0; + m_IsFlying = true; + + // Send the client its fake speed and max speed of 0 + GetClientHandle()->SendPlayerMoveLook(); + GetClientHandle()->SendPlayerAbilities(); + GetClientHandle()->SendPlayerMaxSpeed(); + GetClientHandle()->SendEntityVelocity(*this); + + // Keep the server side speed variables as they were in the first place + m_NormalMaxSpeed = NormalMaxSpeed; + m_SprintingMaxSpeed = SprintMaxSpeed; + m_FlyingMaxSpeed = FlyingMaxpeed; + m_IsFlying = IsFlying; } @@ -1475,8 +1458,10 @@ void cPlayer::Unfreeze() GetClientHandle()->SendPlayerMaxSpeed(); m_IsFrozen = false; - BroadcastMovementUpdate(GetClientHandle()); - GetClientHandle()->SendPlayerPosition(); + DO_WITH_VALID_CLIENTHANDLE( + SendPlayerPosition(); + BroadcastMovementUpdate(ClientHandle.get()); + ) } @@ -1487,7 +1472,9 @@ void cPlayer::SendRotation(double a_YawDegrees, double a_PitchDegrees) { SetYaw(a_YawDegrees); SetPitch(a_PitchDegrees); - m_ClientHandle->SendPlayerMoveLook(); + DO_WITH_VALID_CLIENTHANDLE( + SendPlayerMoveLook(); + ) } @@ -1524,15 +1511,6 @@ Vector3d cPlayer::GetThrowSpeed(double a_SpeedCoeff) const -void cPlayer::ForceSetSpeed(const Vector3d & a_Speed) -{ - SetSpeed(a_Speed); -} - - - - - void cPlayer::DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) { if (m_IsFrozen) @@ -1542,7 +1520,9 @@ void cPlayer::DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) } super::DoSetSpeed(a_SpeedX, a_SpeedY, a_SpeedZ); // Send the speed to the client so he actualy moves - m_ClientHandle->SendEntityVelocity(*this); + DO_WITH_VALID_CLIENTHANDLE( + SendEntityVelocity(*this); + ) } @@ -1560,10 +1540,120 @@ void cPlayer::SetVisible(bool a_bVisible) if (!a_bVisible && m_bVisible) { m_bVisible = false; - m_World->BroadcastDestroyEntity(*this, m_ClientHandle.get()); // Destroy on all clients + m_World->BroadcastDestroyEntity(*this, m_ClientHandle.lock().get()); // Destroy on all clients } } +void cPlayer::SendMessage(const AString & a_Message) +{ + SendMessage(cCompositeChat(a_Message, mtCustom)); +} + + + + + +void cPlayer::SendMessageInfo(const AString & a_Message) +{ + SendMessage(cCompositeChat(a_Message, mtInformation)); +} + + + + + +void cPlayer::SendMessageFailure(const AString & a_Message) +{ + SendMessage(cCompositeChat(a_Message, mtFailure)); +} + + + + + +void cPlayer::SendMessageSuccess(const AString & a_Message) +{ + SendMessage(cCompositeChat(a_Message, mtSuccess)); +} + + + + + +void cPlayer::SendMessageWarning(const AString & a_Message) +{ + SendMessage(cCompositeChat(a_Message, mtWarning)); +} + + + + + +void cPlayer::SendMessageFatal(const AString & a_Message) +{ + SendMessage(cCompositeChat(a_Message, mtFailure)); +} + + + + + +void cPlayer::SendMessagePrivateMsg(const AString & a_Message, const AString & a_Sender) +{ + SendMessage(cCompositeChat(a_Message, mtPrivateMessage, a_Sender)); +} + + + + + +void cPlayer::SendMessage(const cCompositeChat & a_Message) +{ + DO_WITH_VALID_CLIENTHANDLE( + SendChat(a_Message); + ) +} + + + + + +void cPlayer::SendSystemMessage(const AString & a_Message) +{ + SendSystemMessage(cCompositeChat(a_Message)); +} + + + + + +void cPlayer::SendAboveActionBarMessage(const AString & a_Message) +{ + SendAboveActionBarMessage(cCompositeChat(a_Message)); +} + + + + + +void cPlayer::SendSystemMessage(const cCompositeChat & a_Message) +{ + DO_WITH_VALID_CLIENTHANDLE( + SendChatSystem(a_Message); + ) +} + + + + + +void cPlayer::SendAboveActionBarMessage(const cCompositeChat & a_Message) +{ + DO_WITH_VALID_CLIENTHANDLE( + SendChatAboveActionBar(a_Message); + ) +} + @@ -1760,77 +1850,94 @@ void cPlayer::TossItems(const cItems & a_Items) -bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) +bool cPlayer::OnPreWorldTravel(cWorld & a_NewWorld) { - ASSERT(a_World != nullptr); - ASSERT(IsTicking()); - - if (GetWorld() == a_World) + if ((GetWorld() == &a_NewWorld) || IsChangingWorlds()) { - // Don't move to same world + // Don't move a) to same world or b) when already moving worlds return false; } + ASSERT(IsTicking()); // Ask the plugins if the player is allowed to changing the world - if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World)) + if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, a_NewWorld)) { // A Plugin doesn't allow the player to changing the world return false; } - // The clienthandle caches the coords of the chunk we're standing at. Invalidate this. - GetClientHandle()->InvalidateCachedSentChunk(); - // Prevent further ticking in this world SetIsTicking(false); - // Tell others we are gone + // Stop sending chunks + SetIsChangingWorlds(true); + + auto ClientHandle = GetClientHandlePtr().lock(); + if (ClientHandle != nullptr) + { + cRoot::Get()->GetServer()->QueueTask( + [ClientHandle] + { + ClientHandle->RemoveFromWorld(); + } + ); + + GetWorld()->RemovePlayer(this); + ClientHandle->SendRespawn(a_NewWorld.GetDimension()); + + switch (a_NewWorld.GetDimension()) + { + case eDimension::dimOverworld: break; + case eDimension::dimNether: AwardAchievement(achEnterPortal); break; + case eDimension::dimEnd: AwardAchievement(achEnterTheEnd); break; + default: ASSERT(!"Unexpected dimension during world travel."); + } + } + // If std::weak_ptr<>::lock returned nullptr, the destructor of cClientHandle would have handled player removal. + + auto Entity = GetParentChunk()->AcquireAssociatedEntityPtr(*this); + + // Broadcast for other people that the player is gone. GetWorld()->BroadcastDestroyEntity(*this); - // Remove player from world - GetWorld()->RemovePlayer(this, false); + cpp14::move_on_copy_wrapper<decltype(Entity)> EntityPtr(std::move(Entity)); + a_NewWorld.QueueTask( + [EntityPtr](cWorld & a_DestinationWorld) mutable + { + ASSERT(EntityPtr.value->IsPlayer()); + auto Player = static_cast<cPlayer *>(EntityPtr.value.get()); - // Set position to the new position - SetPosition(a_NewPosition); - FreezeInternal(a_NewPosition, false); + // Player changed world, call the hook + cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*EntityPtr.value, *EntityPtr.value->GetWorld()); - // Stop all mobs from targeting this player - StopEveryoneFromTargetingMe(); + EntityPtr.value->Initialize(std::move(EntityPtr.value), a_DestinationWorld); + a_DestinationWorld.AddPlayer(Player); - // Send the respawn packet: - if (a_ShouldSendRespawn && (m_ClientHandle != nullptr)) - { - m_ClientHandle->SendRespawn(a_World->GetDimension()); - } + Player->SetIsChangingWorlds(false); + } + ); + + return true; +} - // Update the view distance. - m_ClientHandle->SetViewDistance(m_ClientHandle->GetRequestedViewDistance()); - // Send current weather of target world to player - if (a_World->GetDimension() == dimOverworld) - { - m_ClientHandle->SendWeather(a_World->GetWeather()); - } - // Broadcast the player into the new world. - a_World->BroadcastSpawnEntity(*this); - // Queue add to new world and removal from the old one - cChunk * ParentChunk = GetParentChunk(); - cWorld * OldWorld = GetWorld(); - SetWorld(a_World); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value - OldWorld->QueueTask([this, ParentChunk, a_World](cWorld & a_OldWorld) + +void cPlayer::OnPostWorldTravel(eDimension a_PreviousDimension, const Vector3d & a_RecommendedPosition) +{ + if ((a_PreviousDimension == eDimension::dimEnd) && (GetWorld()->GetDimension() == eDimension::dimOverworld)) { - 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() - ); - ParentChunk->RemoveEntity(this); - a_World->AddPlayer(this); - cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*this, a_OldWorld); - }); - return true; + super::OnPostWorldTravel(a_PreviousDimension, GetLastBedPos()); + } + else + { + super::OnPostWorldTravel(a_PreviousDimension, a_RecommendedPosition); + } + + DO_WITH_VALID_CLIENTHANDLE( + SendPlayerMoveLook(); + ) } @@ -2251,12 +2358,6 @@ bool cPlayer::IsClimbing(void) const void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIsOnGround) { - if (m_bIsTeleporting) - { - m_bIsTeleporting = false; - return; - } - StatValue Value = FloorC<StatValue>(a_DeltaPos.Length() * 100 + 0.5); if (m_AttachedTo == nullptr) { @@ -2412,7 +2513,9 @@ void cPlayer::SendBlocksAround(int a_BlockX, int a_BlockY, int a_BlockZ, int a_R // Send the blocks for each affected chunk: for (auto itr = Changes.cbegin(), end = Changes.cend(); itr != end; ++itr) { - m_ClientHandle->SendBlockChanges(itr->first.m_ChunkX, itr->first.m_ChunkZ, itr->second); + DO_WITH_VALID_CLIENTHANDLE( + SendBlockChanges(itr->first.m_ChunkX, itr->first.m_ChunkZ, itr->second); + ) } } @@ -2449,7 +2552,7 @@ bool cPlayer::PlaceBlocks(const sSetBlockVector & a_Blocks) } // Call the "placed" hooks: - for (auto blk: a_Blocks) + for (const auto & blk: a_Blocks) { pm->CallHookPlayerPlacedBlock(*this, blk); } @@ -2482,7 +2585,7 @@ void cPlayer::Detach() cBlockInfo::IsSolid(m_World->GetBlock(x, y - 1, z)) ) { - TeleportToCoords(x + 0.5, y, z + 0.5); + TeleportToCoords(Vector3d(x + 0.5, y, z + 0.5)); return; } } @@ -2494,16 +2597,6 @@ void cPlayer::Detach() -void cPlayer::RemoveClientHandle(void) -{ - ASSERT(m_ClientHandle != nullptr); - m_ClientHandle.reset(); -} - - - - - AString cPlayer::GetUUIDFileName(const AString & a_UUID) { AString UUID = cMojangAPI::MakeUUIDDashed(a_UUID); @@ -2516,38 +2609,3 @@ AString cPlayer::GetUUIDFileName(const AString & a_UUID) res.append(".json"); return res; } - - - - - -void cPlayer::FreezeInternal(const Vector3d & a_Location, bool a_ManuallyFrozen) -{ - SetSpeed(0, 0, 0); - SetPosition(a_Location); - m_IsFrozen = true; - m_IsManuallyFrozen = a_ManuallyFrozen; - - double NormalMaxSpeed = GetNormalMaxSpeed(); - double SprintMaxSpeed = GetSprintingMaxSpeed(); - double FlyingMaxpeed = GetFlyingMaxSpeed(); - bool IsFlying = m_IsFlying; - - // Set the client-side speed to 0 - m_NormalMaxSpeed = 0; - m_SprintingMaxSpeed = 0; - m_FlyingMaxSpeed = 0; - m_IsFlying = true; - - // Send the client its fake speed and max speed of 0 - GetClientHandle()->SendPlayerMoveLook(); - GetClientHandle()->SendPlayerAbilities(); - GetClientHandle()->SendPlayerMaxSpeed(); - GetClientHandle()->SendEntityVelocity(*this); - - // Keep the server side speed variables as they were in the first place - m_NormalMaxSpeed = NormalMaxSpeed; - m_SprintingMaxSpeed = SprintMaxSpeed; - m_FlyingMaxSpeed = FlyingMaxpeed; - m_IsFlying = IsFlying; -} diff --git a/src/Entities/Player.h b/src/Entities/Player.h index f6e9da45e..c8e7b8297 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -40,9 +40,7 @@ public: CLASS_PROTODEF(cPlayer) - cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName); - - virtual bool Initialize(cWorld & a_World) override; + cPlayer(std::weak_ptr<cClientHandle> a_Client, const AString & a_PlayerName); virtual ~cPlayer(); @@ -138,7 +136,7 @@ public: /** Returns whether the player is climbing (ladders, vines etc.) */ bool IsClimbing(void) const; - virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) override; + virtual void TeleportToCoords(const Vector3d & a_Position) override; // tolua_begin @@ -217,10 +215,6 @@ public: void SetIP(const AString & a_IP); - /** Forces the player to move in the given direction. - @deprecated Use SetSpeed instead. */ - void ForceSetSpeed(const Vector3d & a_Speed); // tolua_export - cWindow * GetWindow(void) { return m_CurrentWindow; } // tolua_export const cWindow * GetWindow(void) const { return m_CurrentWindow; } @@ -236,28 +230,28 @@ public: void CloseWindowIfID(char a_WindowID, bool a_CanRefuse = true); /** Returns the raw client handle associated with the player. */ - cClientHandle * GetClientHandle(void) const { return m_ClientHandle.get(); } + cClientHandle * GetClientHandle(void) const { return m_ClientHandle.lock().get(); } // tolua_end /** Returns the SharedPtr to client handle associated with the player. */ - cClientHandlePtr GetClientHandlePtr(void) const { return m_ClientHandle; } + std::weak_ptr<cClientHandle> GetClientHandlePtr(void) const { return m_ClientHandle; } // tolua_begin - void SendMessage (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtCustom); } - void SendMessageInfo (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtInformation); } - void SendMessageFailure (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtFailure); } - void SendMessageSuccess (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtSuccess); } - void SendMessageWarning (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtWarning); } - void SendMessageFatal (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtFailure); } - void SendMessagePrivateMsg (const AString & a_Message, const AString & a_Sender) { m_ClientHandle->SendChat(a_Message, mtPrivateMessage, a_Sender); } - void SendMessage (const cCompositeChat & a_Message) { m_ClientHandle->SendChat(a_Message); } + void SendMessage(const AString & a_Message); + void SendMessageInfo(const AString & a_Message); + void SendMessageFailure(const AString & a_Message); + void SendMessageSuccess(const AString & a_Message); + void SendMessageWarning(const AString & a_Message); + void SendMessageFatal(const AString & a_Message); + void SendMessagePrivateMsg(const AString & a_Message, const AString & a_Sender); + void SendMessage(const cCompositeChat & a_Message); - void SendSystemMessage (const AString & a_Message) { m_ClientHandle->SendChatSystem(a_Message, mtCustom); } - void SendAboveActionBarMessage(const AString & a_Message) { m_ClientHandle->SendChatAboveActionBar(a_Message, mtCustom); } - void SendSystemMessage (const cCompositeChat & a_Message) { m_ClientHandle->SendChatSystem(a_Message); } - void SendAboveActionBarMessage(const cCompositeChat & a_Message) { m_ClientHandle->SendChatAboveActionBar(a_Message); } + void SendSystemMessage(const AString & a_Message); + void SendAboveActionBarMessage(const AString & a_Message); + void SendSystemMessage(const cCompositeChat & a_Message); + void SendAboveActionBarMessage(const cCompositeChat & a_Message); const AString & GetName(void) const { return m_PlayerName; } void SetName(const AString & a_Name) { m_PlayerName = a_Name; } @@ -266,7 +260,7 @@ public: bool HasPermission(const AString & a_Permission); // tolua_export - /** Returns true iff a_Permission matches the a_Template. + /** Returns true if a_Permission matches the a_Template. A match is defined by either being exactly the same, or each sub-item matches until there's a wildcard in a_Template. Ie. {"a", "b", "c"} matches {"a", "b", "*"} but doesn't match {"a", "b"} */ static bool PermissionMatches(const AStringVector & a_Permission, const AStringVector & a_Template); // Exported in ManualBindings with AString params @@ -367,12 +361,22 @@ public: 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; + Overloads cEntity to perform additional housekeeping with regards to the clienthandle and world. */ + virtual bool OnPreWorldTravel(cWorld & a_NewWorld) override; + + /** Sets the player to the correct coordinates having changed world. + Overloads cEntity to ensure we return to our own spawn point (not the world spawn) when returning from the End. */ + virtual void OnPostWorldTravel(eDimension a_PreviousDimension, const Vector3d & a_RecommendedPosition) override; /** Saves all player data, such as inventory, to JSON */ bool SaveToDisk(void); + /** Returns whether the player is in the process of changing worlds in a thread-safe manner. */ + bool IsChangingWorlds(void) const { return m_IsChangingWorlds; } + + /** Sets whether the player is in the process of changing worlds in a thread-safe manner. */ + void SetIsChangingWorlds(bool a_Flag) { m_IsChangingWorlds = a_Flag; } + typedef cWorld * cWorldPtr; /** Loads the player data from the disk file @@ -517,10 +521,6 @@ public: virtual void Detach(void) override; - /** Called by cClientHandle when the client is being destroyed. - The player removes its m_ClientHandle ownership so that the ClientHandle gets deleted. */ - void RemoveClientHandle(void); - protected: typedef std::vector<std::vector<AString> > AStringVectorVector; @@ -598,16 +598,13 @@ protected: std::chrono::steady_clock::time_point m_LastPlayerListTime; - cClientHandlePtr m_ClientHandle; + std::weak_ptr<cClientHandle> m_ClientHandle; cSlotNums m_InventoryPaintSlots; /** If true, we are locking m_Position to m_FrozenPosition. */ bool m_IsFrozen; - /** Was the player frozen manually by a plugin or automatically by the server? */ - bool m_IsManuallyFrozen; - /** Max speed, relative to the game default. 1 means regular speed, 2 means twice as fast, 0.5 means half-speed. Default value is 1. */ @@ -659,10 +656,10 @@ protected: Default save interval is #defined in PLAYER_INVENTORY_SAVE_INTERVAL */ unsigned int m_TicksUntilNextSave; - /** Flag used by food handling system to determine whether a teleport has just happened - Will not apply food penalties if found to be true; will set to false after processing - */ - bool m_bIsTeleporting; + /** Flag indicating whether the entity is in the process of changing worlds. + MAY be accessed concurrently. + Used currently to prevent chunk sending before the player is added to the destination world. */ + std::atomic_bool m_IsChangingWorlds; /** The short UUID (no dashes) of the player, as read from the ClientHandle. If no ClientHandle is given, the UUID is initialized to empty. */ @@ -673,9 +670,6 @@ protected: /** 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; - void ResolvePermissions(void); - void ResolveGroups(void); - virtual void Destroyed(void) override; /** Filters out damage for creative mode / friendly fire */ @@ -697,9 +691,4 @@ protected: This can be used both for online and offline UUIDs. */ AString GetUUIDFileName(const AString & a_UUID); -private: - - /** Pins the player to a_Location until Unfreeze() is called. - If ManuallyFrozen is false, the player will unfreeze when the chunk is loaded. */ - void FreezeInternal(const Vector3d & a_Location, bool a_ManuallyFrozen); -} ; // tolua_export +}; // tolua_export diff --git a/src/Generating/ChunkDesc.cpp b/src/Generating/ChunkDesc.cpp index 6ba63d5ce..b7f189fea 100644 --- a/src/Generating/ChunkDesc.cpp +++ b/src/Generating/ChunkDesc.cpp @@ -8,7 +8,8 @@ #include "../BlockArea.h" #include "../Cuboid.h" #include "../Noise/Noise.h" -#include "../BlockEntities/BlockEntity.h" +#include "BlockEntities/BlockEntity.h" +#include "Entities/Entity.h" diff --git a/src/Generating/ChunkGenerator.cpp b/src/Generating/ChunkGenerator.cpp index e7c2e4125..c5d830d0d 100644 --- a/src/Generating/ChunkGenerator.cpp +++ b/src/Generating/ChunkGenerator.cpp @@ -15,9 +15,6 @@ /** If the generation queue size exceeds this number, a warning will be output */ const unsigned int QUEUE_WARNING_LIMIT = 1000; -/** If the generation queue size exceeds this number, chunks with no clients will be skipped */ -const unsigned int QUEUE_SKIP_LIMIT = 500; - @@ -114,6 +111,11 @@ void cChunkGenerator::QueueGenerateChunk(int a_ChunkX, int a_ChunkZ, bool a_Forc { ASSERT(m_ChunkSink->IsChunkQueued(a_ChunkX, a_ChunkZ)); + if (!a_ForceGenerate && m_ChunkSink->IsChunkValid(a_ChunkX, a_ChunkZ)) + { + return; + } + { cCSLock Lock(m_CS); @@ -122,7 +124,7 @@ void cChunkGenerator::QueueGenerateChunk(int a_ChunkX, int a_ChunkZ, bool a_Forc { LOGWARN("WARNING: Adding chunk [%i, %i] to generation queue; Queue is too big! (" SIZE_T_FMT ")", a_ChunkX, a_ChunkZ, m_Queue.size()); } - m_Queue.push_back(cQueueItem{a_ChunkX, a_ChunkZ, a_ForceGenerate, a_Callback}); + m_Queue.emplace_back(sQueueItem{ a_ChunkX, a_ChunkZ, a_Callback }); } m_Event.Set(); @@ -144,20 +146,6 @@ void cChunkGenerator::GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::Biom -void cChunkGenerator::WaitForQueueEmpty(void) -{ - cCSLock Lock(m_CS); - while (!m_ShouldTerminate && !m_Queue.empty()) - { - cCSUnlock Unlock(Lock); - m_evtRemoved.Wait(); - } -} - - - - - int cChunkGenerator::GetQueueLength(void) { cCSLock Lock(m_CS); @@ -204,80 +192,39 @@ void cChunkGenerator::Execute(void) while (!m_ShouldTerminate) { - cCSLock Lock(m_CS); - while (m_Queue.empty()) - { - if ((NumChunksGenerated > 16) && (clock() - LastReportTick > CLOCKS_PER_SEC)) - { - /* LOG("Chunk generator performance: %.2f ch / sec (%d ch total)", - static_cast<double>(NumChunksGenerated) * CLOCKS_PER_SEC/ (clock() - GenerationStart), - NumChunksGenerated - ); */ - } - cCSUnlock Unlock(Lock); - m_Event.Wait(); - if (m_ShouldTerminate) - { - return; - } - NumChunksGenerated = 0; - GenerationStart = clock(); - LastReportTick = clock(); - } + m_Event.Wait(); - if (m_Queue.empty()) + decltype(m_Queue) QueuedChunks; { - // Sometimes the queue remains empty - // If so, we can't do any front() operations on it! - continue; + cCSLock Lock(m_CS); + std::swap(QueuedChunks, m_Queue); } - cQueueItem item = m_Queue.front(); // Get next chunk from the queue - bool SkipEnabled = (m_Queue.size() > QUEUE_SKIP_LIMIT); - m_Queue.erase(m_Queue.begin()); // Remove the item from the queue - Lock.Unlock(); // Unlock ASAP - m_evtRemoved.Set(); - - // Display perf info once in a while: - if ((NumChunksGenerated > 512) && (clock() - LastReportTick > 2 * CLOCKS_PER_SEC)) - { - LOG("Chunk generator performance: %.2f ch / sec (%d ch total)", - static_cast<double>(NumChunksGenerated) * CLOCKS_PER_SEC / (clock() - GenerationStart), - NumChunksGenerated - ); - LastReportTick = clock(); - } + NumChunksGenerated = 0; + GenerationStart = clock(); + LastReportTick = clock(); - // Skip the chunk if it's already generated and regeneration is not forced. Report as success: - if (!item.m_ForceGenerate && m_ChunkSink->IsChunkValid(item.m_ChunkX, item.m_ChunkZ)) + for (const auto & Item : QueuedChunks) { - LOGD("Chunk [%d, %d] already generated, skipping generation", item.m_ChunkX, item.m_ChunkZ); - if (item.m_Callback != nullptr) + // Display perf info once in a while: + if ((NumChunksGenerated > 512) && (clock() - LastReportTick > 2 * CLOCKS_PER_SEC)) { - item.m_Callback->Call(item.m_ChunkX, item.m_ChunkZ, true); + LOG("Chunk generator performance: %.2f ch / sec (%d ch total)", + static_cast<double>(NumChunksGenerated) * CLOCKS_PER_SEC / (clock() - GenerationStart), + NumChunksGenerated + ); + LastReportTick = clock(); } - continue; - } - // Skip the chunk if the generator is overloaded: - if (SkipEnabled && !m_ChunkSink->HasChunkAnyClients(item.m_ChunkX, item.m_ChunkZ)) - { - LOGWARNING("Chunk generator overloaded, skipping chunk [%d, %d]", item.m_ChunkX, item.m_ChunkZ); - if (item.m_Callback != nullptr) + // Generate the chunk: + // LOGD("Generating chunk [%d, %d]", Item.m_ChunkX, Item.m_ChunkZ); + DoGenerate(Item.m_ChunkX, Item.m_ChunkZ); + if (Item.m_Callback != nullptr) { - item.m_Callback->Call(item.m_ChunkX, item.m_ChunkZ, false); + Item.m_Callback->Call(Item.m_ChunkX, Item.m_ChunkZ, true); } - continue; + NumChunksGenerated++; } - - // Generate the chunk: - // LOGD("Generating chunk [%d, %d]", item.m_ChunkX, item.m_ChunkZ); - DoGenerate(item.m_ChunkX, item.m_ChunkZ); - if (item.m_Callback != nullptr) - { - item.m_Callback->Call(item.m_ChunkX, item.m_ChunkZ, true); - } - NumChunksGenerated++; } // while (!bStop) } @@ -288,7 +235,6 @@ void cChunkGenerator::DoGenerate(int a_ChunkX, int a_ChunkZ) { ASSERT(m_PluginInterface != nullptr); ASSERT(m_ChunkSink != nullptr); - ASSERT(m_ChunkSink->IsChunkQueued(a_ChunkX, a_ChunkZ)); cChunkDesc ChunkDesc(a_ChunkX, a_ChunkZ); m_PluginInterface->CallHookChunkGenerating(ChunkDesc); diff --git a/src/Generating/ChunkGenerator.h b/src/Generating/ChunkGenerator.h index 5c778fda4..4e5c045a0 100644 --- a/src/Generating/ChunkGenerator.h +++ b/src/Generating/ChunkGenerator.h @@ -9,7 +9,6 @@ The requests are not added to the queue if there is already a request with the s Before generating, the thread checks if the chunk hasn't been already generated. It is theoretically possible to have multiple generator threads by having multiple instances of this object, but then it MAY happen that the chunk is generated twice. -If the generator queue is overloaded, the generator skips chunks with no clients in them */ @@ -102,8 +101,7 @@ public: If this callback returns false, the chunk is not generated. */ virtual bool HasChunkAnyClients(int a_ChunkX, int a_ChunkZ) = 0; - /** Called to check whether the specified chunk is in the queued state. - Currently used only in Debug-mode asserts. */ + /** Called to check whether the specified chunk is in the queued state. */ virtual bool IsChunkQueued(int a_ChunkX, int a_ChunkZ) = 0; } ; @@ -117,15 +115,12 @@ public: /** Queues the chunk for generation If a-ForceGenerate is set, the chunk is regenerated even if the data is already present in the chunksink. a_Callback is called after the chunk is generated. If the chunk was already present, the callback is still called, even if not regenerating. - It is legal to set the callback to nullptr, no callback is called then. - If the generator becomes overloaded and skips this chunk, the callback is still called. */ + It is legal to set the callback to nullptr, no callback is called then. */ void QueueGenerateChunk(int a_ChunkX, int a_ChunkZ, bool a_ForceGenerate, cChunkCoordCallback * a_Callback = nullptr); /** Generates the biomes for the specified chunk (directly, not in a separate thread). Used by the world loader if biomes failed loading. */ void GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap); - void WaitForQueueEmpty(void); - int GetQueueLength(void); int GetSeed(void) const { return m_Seed; } @@ -138,20 +133,15 @@ public: private: - struct cQueueItem + struct sQueueItem { /** The chunk coords */ int m_ChunkX, m_ChunkZ; - /** Force the regeneration of an already existing chunk */ - bool m_ForceGenerate; - /** Callback to call after generating. */ cChunkCoordCallback * m_Callback; }; - typedef std::list<cQueueItem> cGenQueue; - /** Seed used for the generator. */ int m_Seed; @@ -160,7 +150,7 @@ private: cCriticalSection m_CS; /** Queue of the chunks to be generated. Protected against multithreaded access by m_CS. */ - cGenQueue m_Queue; + std::vector<sQueueItem> m_Queue; /** Set when an item is added to the queue or the thread should terminate. */ cEvent m_Event; diff --git a/src/Globals.h b/src/Globals.h index e3a537eaa..0ab78121c 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -444,17 +444,49 @@ typename std::enable_if<std::is_arithmetic<T>::value, C>::type CeilC(T a_Value) return static_cast<C>(std::ceil(a_Value)); } - - -//temporary replacement for std::make_unique until we get c++14 - namespace cpp14 { + // Temporary replacement for std::make_unique until we get c++14 template <class T, class... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); } + + // Temporary workaround for ... + template <typename StorageType> + struct move_on_copy_wrapper + { + move_on_copy_wrapper(StorageType && a_Value) : + value(std::move(a_Value)) + { + } + + move_on_copy_wrapper(const move_on_copy_wrapper & a_Other) : + value(std::move(a_Other.value)) + { + } + + move_on_copy_wrapper& operator=(const move_on_copy_wrapper & a_Other) + { + value = std::move(a_Other.value); + return *this; + } + + mutable StorageType value; + }; +} + +namespace std +{ + template <typename WeakPtrType> + struct equal_to<std::weak_ptr<WeakPtrType>> + { + /* constexpr */ bool operator()(const std::weak_ptr<WeakPtrType> & a_Lhs, const std::weak_ptr<WeakPtrType> & a_Rhs) const + { + return (!a_Lhs.owner_before(a_Rhs) && !a_Rhs.owner_before(a_Lhs)); + } + }; } // a tick is 50 ms diff --git a/src/LightingThread.cpp b/src/LightingThread.cpp index 4e2826778..6f51e173c 100644 --- a/src/LightingThread.cpp +++ b/src/LightingThread.cpp @@ -20,62 +20,20 @@ class cReader : { virtual void ChunkData(const cChunkData & a_ChunkBuffer) override { - BLOCKTYPE * OutputRows = m_BlockTypes; - int InputIdx = 0; - int OutputIdx = m_ReadingChunkX + m_ReadingChunkZ * cChunkDef::Width * 3; - int MaxHeight = std::min(+cChunkDef::Height, m_MaxHeight + 16); // Need 16 blocks above the highest - for (int y = 0; y < MaxHeight; y++) - { - for (int z = 0; z < cChunkDef::Width; z++) - { - a_ChunkBuffer.CopyBlockTypes(OutputRows + OutputIdx * 16, static_cast<size_t>(InputIdx * 16), 16); - InputIdx++; - OutputIdx += 3; - } // for z - // Skip into the next y-level in the 3x3 chunk blob; each level has cChunkDef::Width * 9 rows - // We've already walked cChunkDef::Width * 3 in the "for z" cycle, that makes cChunkDef::Width * 6 rows left to skip - OutputIdx += cChunkDef::Width * 6; - } // for y - } // BlockTypes() - + a_ChunkBuffer.CopyBlockTypes(m_BlockTypes.data()); + } virtual void HeightMap(const cChunkDef::HeightMap * a_Heightmap) override { - // Copy the entire heightmap, distribute it into the 3x3 chunk blob: - typedef struct {HEIGHTTYPE m_Row[16]; } ROW; - const ROW * InputRows = reinterpret_cast<const ROW *>(a_Heightmap); - ROW * OutputRows = reinterpret_cast<ROW *>(m_HeightMap); - int InputIdx = 0; - int OutputIdx = m_ReadingChunkX + m_ReadingChunkZ * cChunkDef::Width * 3; - for (int z = 0; z < cChunkDef::Width; z++) - { - OutputRows[OutputIdx] = InputRows[InputIdx++]; - OutputIdx += 3; - } // for z - - // Find the highest block in the entire chunk, use it as a base for m_MaxHeight: - HEIGHTTYPE MaxHeight = m_MaxHeight; - for (size_t i = 0; i < ARRAYCOUNT(*a_Heightmap); i++) - { - if ((*a_Heightmap)[i] > MaxHeight) - { - MaxHeight = (*a_Heightmap)[i]; - } - } - m_MaxHeight = MaxHeight; + std::copy(std::begin(*a_Heightmap), std::end(*a_Heightmap), std::begin(m_HeightMap)); } public: - int m_ReadingChunkX; // 0, 1 or 2; x-offset of the chunk we're reading from the BlockTypes start - int m_ReadingChunkZ; // 0, 1 or 2; z-offset of the chunk we're reading from the BlockTypes start - HEIGHTTYPE m_MaxHeight; // Maximum value in this chunk's heightmap - BLOCKTYPE * m_BlockTypes; // 3x3 chunks of block types, organized as a single XZY blob of data (instead of 3x3 XZY blobs) - HEIGHTTYPE * m_HeightMap; // 3x3 chunks of height map, organized as a single XZY blob of data (instead of 3x3 XZY blobs) - - cReader(BLOCKTYPE * a_BlockTypes, HEIGHTTYPE * a_HeightMap) : - m_ReadingChunkX(0), - m_ReadingChunkZ(0), - m_MaxHeight(0), + + cLightingThread::BlockDataArray & m_BlockTypes; // 3x3 chunks of block types, organized as a single XZY blob of data (instead of 3x3 XZY blobs) + cLightingThread::HeightDataArray & m_HeightMap; // 3x3 chunks of height map, organized as a single XZY blob of data (instead of 3x3 XZY blobs) + + cReader(cLightingThread::BlockDataArray & a_BlockTypes, cLightingThread::HeightDataArray & a_HeightMap) : m_BlockTypes(a_BlockTypes), m_HeightMap(a_HeightMap) { @@ -91,9 +49,7 @@ public: cLightingThread::cLightingThread(void) : super("cLightingThread"), - m_World(nullptr), - m_MaxHeight(0), - m_NumSeeds(0) + m_World(nullptr) { } @@ -124,24 +80,8 @@ bool cLightingThread::Start(cWorld * a_World) void cLightingThread::Stop(void) { - { - cCSLock Lock(m_CS); - for (cChunkStays::iterator itr = m_PendingQueue.begin(), end = m_PendingQueue.end(); itr != end; ++itr) - { - (*itr)->Disable(); - delete *itr; - } - m_PendingQueue.clear(); - for (cChunkStays::iterator itr = m_Queue.begin(), end = m_Queue.end(); itr != end; ++itr) - { - (*itr)->Disable(); - delete *itr; - } - m_Queue.clear(); - } m_ShouldTerminate = true; - m_evtItemAdded.Set(); - + m_evtItemDataLoaded.Set(); Wait(); } @@ -153,28 +93,25 @@ void cLightingThread::QueueChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr<cCh { ASSERT(m_World != nullptr); // Did you call Start() properly? - cChunkStay * ChunkStay = new cLightingChunkStay(*this, a_ChunkX, a_ChunkZ, std::move(a_CallbackAfter)); + // If the chunk is already lit, skip it: + if (m_World->IsChunkValid(a_ChunkX, a_ChunkZ) && m_World->IsChunkLighted(a_ChunkX, a_ChunkZ)) { - // The ChunkStay will enqueue itself using the QueueChunkStay() once it is fully loaded - // In the meantime, put it into the PendingQueue so that it can be removed when stopping the thread - cCSLock Lock(m_CS); - m_PendingQueue.push_back(ChunkStay); + if (a_CallbackAfter != nullptr) + { + a_CallbackAfter->Call(a_ChunkX, a_ChunkZ, true); + } + return; } - ChunkStay->Enable(*m_World->GetChunkMap()); -} - - - -void cLightingThread::WaitForQueueEmpty(void) -{ - cCSLock Lock(m_CS); - while (!m_ShouldTerminate && (!m_Queue.empty() || !m_PendingQueue.empty())) + auto ChunkStay = new cLightingChunkStay(*this, a_ChunkX, a_ChunkZ, std::move(a_CallbackAfter)); { - cCSUnlock Unlock(Lock); - m_evtQueueEmpty.Wait(); + // The ChunkStay will enqueue itself using the QueueChunkStay() once it is fully loaded + // In the meantime, put it into the PendingQueue so that it can be removed when stopping the thread + cCSLock Lock(m_CS); + m_PendingQueue.emplace_back(ChunkStay); } + ChunkStay->Enable(*m_World->GetChunkMap()); } @@ -184,7 +121,7 @@ void cLightingThread::WaitForQueueEmpty(void) size_t cLightingThread::GetQueueLength(void) { cCSLock Lock(m_CS); - return m_Queue.size() + m_PendingQueue.size(); + return m_PendingQueue.size(); } @@ -193,69 +130,52 @@ size_t cLightingThread::GetQueueLength(void) void cLightingThread::Execute(void) { - for (;;) + while (!m_ShouldTerminate) { + m_evtItemDataLoaded.Wait(); + + decltype(m_Queue) QueuedChunks; { cCSLock Lock(m_CS); - if (m_Queue.empty()) - { - cCSUnlock Unlock(Lock); - m_evtItemAdded.Wait(); - } + std::swap(m_Queue, QueuedChunks); } - if (m_ShouldTerminate) + for (const auto & Entry : QueuedChunks) { - return; + Entry->m_BlockTypes = cpp14::make_unique<BlockDataArray>(); + Entry->m_HeightMap = cpp14::make_unique<HeightDataArray>(); } - // Process one items from the queue: - cLightingChunkStay * Item; - { - cCSLock Lock(m_CS); - if (m_Queue.empty()) - { - continue; - } - Item = static_cast<cLightingChunkStay *>(m_Queue.front()); - m_Queue.pop_front(); - if (m_Queue.empty()) + m_World->QueueTask( + [&QueuedChunks](cWorld & a_World) { - m_evtQueueEmpty.Set(); + for (const auto & Entry : QueuedChunks) + { + cReader Reader(*Entry->m_BlockTypes, *Entry->m_HeightMap); + VERIFY(a_World.GetChunkData(Entry->m_ChunkX, Entry->m_ChunkZ, Reader)); + } } - } // CSLock(m_CS) + ).wait(); - LightChunk(*Item); - Item->Disable(); - delete Item; - } + for (const auto & Entry : QueuedChunks) + { + LightChunk(*Entry); + } + } // while (!m_ShouldTerminate) } - void cLightingThread::LightChunk(cLightingChunkStay & a_Item) { - // If the chunk is already lit, skip it (report as success): - if (m_World->IsChunkLighted(a_Item.m_ChunkX, a_Item.m_ChunkZ)) - { - if (a_Item.m_CallbackAfter != nullptr) - { - a_Item.m_CallbackAfter->Call(a_Item.m_ChunkX, a_Item.m_ChunkZ, true); - } - return; - } - - cChunkDef::BlockNibbles BlockLight, SkyLight; - - ReadChunks(a_Item.m_ChunkX, a_Item.m_ChunkZ); + std::unique_ptr<BlockDataArray> + BlockLight{ cpp14::make_unique<decltype(BlockLight)::element_type>() }, + SkyLight{ cpp14::make_unique<decltype(BlockLight)::element_type>() } + ; - PrepareBlockLight(); - CalcLight(m_BlockLight); - - PrepareSkyLight(); + CalcLight(a_Item, PrepareBlockLight(a_Item, *BlockLight), *BlockLight); /* // DEBUG: Save chunk data with highlighted seeds for visual inspection: @@ -269,10 +189,10 @@ void cLightingThread::LightChunk(cLightingChunkStay & a_Item) for (int y = cChunkDef::Height / 2; y >= 0; y--) { unsigned char Seeds [cChunkDef::Width * 3]; - memcpy(Seeds, m_BlockTypes + y * BlocksPerYLayer + z * cChunkDef::Width * 3, cChunkDef::Width * 3); + memcpy(Seeds, m_BlockTypes + y * cLightingChunkStay::BlocksPerYLayer + z * cChunkDef::Width * 3, cChunkDef::Width * 3); for (int x = 0; x < cChunkDef::Width * 3; x++) { - if (m_IsSeed1[y * BlocksPerYLayer + z * cChunkDef::Width * 3 + x]) + if (m_IsSeed1[y * cLightingChunkStay::BlocksPerYLayer + z * cChunkDef::Width * 3 + x]) { Seeds[x] = E_BLOCK_DIAMOND_BLOCK; } @@ -284,7 +204,7 @@ void cLightingThread::LightChunk(cLightingChunkStay & a_Item) } //*/ - CalcLight(m_SkyLight); + CalcLight(a_Item, PrepareSkyLight(a_Item, *SkyLight), *SkyLight); /* // DEBUG: Save XY slices of the chunk data and lighting for visual inspection: @@ -299,13 +219,13 @@ void cLightingThread::LightChunk(cLightingChunkStay & a_Item) { for (int y = cChunkDef::Height / 2; y >= 0; y--) { - f1.Write(m_BlockTypes + y * BlocksPerYLayer + z * cChunkDef::Width * 3, cChunkDef::Width * 3); + f1.Write(m_BlockTypes + y * cLightingChunkStay::BlocksPerYLayer + z * cChunkDef::Width * 3, cChunkDef::Width * 3); unsigned char SkyLight [cChunkDef::Width * 3]; unsigned char BlockLight[cChunkDef::Width * 3]; for (int x = 0; x < cChunkDef::Width * 3; x++) { - SkyLight[x] = m_SkyLight [y * BlocksPerYLayer + z * cChunkDef::Width * 3 + x] << 4; - BlockLight[x] = m_BlockLight[y * BlocksPerYLayer + z * cChunkDef::Width * 3 + x] << 4; + SkyLight[x] = m_SkyLight [y * cLightingChunkStay::BlocksPerYLayer + z * cChunkDef::Width * 3 + x] << 4; + BlockLight[x] = m_BlockLight[y * cLightingChunkStay::BlocksPerYLayer + z * cChunkDef::Width * 3 + x] << 4; } f2.Write(SkyLight, cChunkDef::Width * 3); f3.Write(BlockLight, cChunkDef::Width * 3); @@ -317,165 +237,91 @@ void cLightingThread::LightChunk(cLightingChunkStay & a_Item) } //*/ - CompressLight(m_BlockLight, BlockLight); - CompressLight(m_SkyLight, SkyLight); - - m_World->ChunkLighted(a_Item.m_ChunkX, a_Item.m_ChunkZ, BlockLight, SkyLight); - - if (a_Item.m_CallbackAfter != nullptr) - { - a_Item.m_CallbackAfter->Call(a_Item.m_ChunkX, a_Item.m_ChunkZ, true); - } -} - - - + std::shared_ptr<BlockNibbles> + CompressedBlockLight{ std::make_shared<decltype(CompressedBlockLight)::element_type>() }, + CompressedSkyLight{ std::make_shared<decltype(CompressedSkyLight)::element_type>() } + ; + CompressLight(*BlockLight, *CompressedBlockLight); + CompressLight(*SkyLight, *CompressedSkyLight); - -void cLightingThread::ReadChunks(int a_ChunkX, int a_ChunkZ) -{ - cReader Reader(m_BlockTypes, m_HeightMap); - - for (int z = 0; z < 3; z++) - { - Reader.m_ReadingChunkZ = z; - for (int x = 0; x < 3; x++) + m_World->QueueTask( + [&a_Item, CompressedBlockLight, CompressedSkyLight](cWorld & a_World) { - Reader.m_ReadingChunkX = x; - VERIFY(m_World->GetChunkData(a_ChunkX + x - 1, a_ChunkZ + z - 1, Reader)); - } // for z - } // for x - - memset(m_BlockLight, 0, sizeof(m_BlockLight)); - memset(m_SkyLight, 0, sizeof(m_SkyLight)); - m_MaxHeight = Reader.m_MaxHeight; + a_World.ChunkLighted( + a_Item.m_ChunkX, + a_Item.m_ChunkZ, + reinterpret_cast<const cChunkDef::BlockNibbles &>(*CompressedBlockLight->data()), + reinterpret_cast<const cChunkDef::BlockNibbles &>(*CompressedSkyLight->data()) + ); + + if (a_Item.m_CallbackAfter != nullptr) + { + a_Item.m_CallbackAfter->Call(a_Item.m_ChunkX, a_Item.m_ChunkZ, true); + } + + a_Item.Disable(); + } + ); } -void cLightingThread::PrepareSkyLight(void) +std::vector<size_t> cLightingThread::PrepareSkyLight(const cLightingChunkStay & a_Item, BlockDataArray & a_SkyLight) { - // Clear seeds: - memset(m_IsSeed1, 0, sizeof(m_IsSeed1)); - m_NumSeeds = 0; + std::vector<size_t> IndicesToProcess; + IndicesToProcess.reserve(a_Item.m_HeightMap->size() * 40); - // Walk every column that has all XZ neighbors - for (int z = 1; z < cChunkDef::Width * 3 - 1; z++) + for (size_t Index = 0; Index != a_Item.m_HeightMap->size(); ++Index) { - int BaseZ = z * cChunkDef::Width * 3; - for (int x = 1; x < cChunkDef::Width * 3 - 1; x++) + for (int Y = cChunkDef::Height - 1; Y >= (*a_Item.m_HeightMap)[Index]; --Y) { - int idx = BaseZ + x; - int Current = m_HeightMap[idx] + 1; - int Neighbor1 = m_HeightMap[idx + 1] + 1; // X + 1 - int Neighbor2 = m_HeightMap[idx - 1] + 1; // X - 1 - int Neighbor3 = m_HeightMap[idx + cChunkDef::Width * 3] + 1; // Z + 1 - int Neighbor4 = m_HeightMap[idx - cChunkDef::Width * 3] + 1; // Z - 1 - int MaxNeighbor = std::max(std::max(Neighbor1, Neighbor2), std::max(Neighbor3, Neighbor4)); // Maximum of the four neighbors - - // Fill the column from the top down to Current with all-light: - for (int y = cChunkDef::Height - 1, Index = idx + y * BlocksPerYLayer; y >= Current; y--, Index -= BlocksPerYLayer) - { - m_SkyLight[Index] = 15; - } + auto HeightAdjustedIndex = Index + Y * cChunkDef::Width * cChunkDef::Width; + a_SkyLight[HeightAdjustedIndex] = 15; - // Add Current as a seed: - if (Current < cChunkDef::Height) - { - int CurrentIdx = idx + Current * BlocksPerYLayer; - m_IsSeed1[CurrentIdx] = true; - m_SeedIdx1[m_NumSeeds++] = static_cast<UInt32>(CurrentIdx); - } - - // Add seed from Current up to the highest neighbor: - for (int y = Current + 1, Index = idx + y * BlocksPerYLayer; y < MaxNeighbor; y++, Index += BlocksPerYLayer) - { - m_IsSeed1[Index] = true; - m_SeedIdx1[m_NumSeeds++] = static_cast<UInt32>(Index); - } + IndicesToProcess.emplace_back(HeightAdjustedIndex); } } + + return IndicesToProcess; } -void cLightingThread::PrepareBlockLight(void) +std::vector<size_t> cLightingThread::PrepareBlockLight(const cLightingChunkStay & a_Item, BlockDataArray & a_BlockLight) { - // Clear seeds: - memset(m_IsSeed1, 0, sizeof(m_IsSeed1)); - memset(m_IsSeed2, 0, sizeof(m_IsSeed2)); - m_NumSeeds = 0; + std::vector<size_t> IndicesToProcess; + IndicesToProcess.reserve(cChunkDef::Width * cChunkDef::Width); - // Walk every column that has all XZ neighbors, make a seed for each light-emitting block: - for (int z = 1; z < cChunkDef::Width * 3 - 1; z++) + for (size_t Index = 0; Index != a_Item.m_BlockTypes->size(); ++Index) { - int BaseZ = z * cChunkDef::Width * 3; - for (int x = 1; x < cChunkDef::Width * 3 - 1; x++) - { - int idx = BaseZ + x; - for (int y = m_HeightMap[idx], Index = idx + y * BlocksPerYLayer; y >= 0; y--, Index -= BlocksPerYLayer) - { - if (cBlockInfo::GetLightValue(m_BlockTypes[Index]) == 0) - { - continue; - } - - // Add current block as a seed: - m_IsSeed1[Index] = true; - m_SeedIdx1[m_NumSeeds++] = static_cast<UInt32>(Index); + auto Light = cBlockInfo::GetLightValue((*a_Item.m_BlockTypes)[Index]); + a_BlockLight[Index] = Light; - // Light it up: - m_BlockLight[Index] = cBlockInfo::GetLightValue(m_BlockTypes[Index]); - } + if (Light > 1) + { + IndicesToProcess.emplace_back(Index); } } + + return IndicesToProcess; } -void cLightingThread::PrepareBlockLight2(void) +void cLightingThread::CalcLight(const cLightingChunkStay & a_Item, std::vector<size_t> a_IndicesToProcess, BlockDataArray & a_Light) { - // Clear seeds: - memset(m_IsSeed1, 0, sizeof(m_IsSeed1)); - memset(m_IsSeed2, 0, sizeof(m_IsSeed2)); - m_NumSeeds = 0; - - // Add each emissive block into the seeds: - for (int y = 0; y < m_MaxHeight; y++) + while (!a_IndicesToProcess.empty()) { - int BaseY = y * BlocksPerYLayer; // Partial offset into m_BlockTypes for the Y coord - for (int z = 1; z < cChunkDef::Width * 3 - 1; z++) - { - int HBaseZ = z * cChunkDef::Width * 3; // Partial offset into m_Heightmap for the Z coord - int BaseZ = BaseY + HBaseZ; // Partial offset into m_BlockTypes for the Y and Z coords - for (int x = 1; x < cChunkDef::Width * 3 - 1; x++) - { - int idx = BaseZ + x; - if (y > m_HeightMap[HBaseZ + x]) - { - // We're above the heightmap, ignore the block - continue; - } - if (cBlockInfo::GetLightValue(m_BlockTypes[idx]) == 0) - { - // Not a light-emissive block - continue; - } + auto Back = a_IndicesToProcess.back(); + a_IndicesToProcess.pop_back(); - // Add current block as a seed: - m_IsSeed1[idx] = true; - m_SeedIdx1[m_NumSeeds++] = static_cast<UInt32>(idx); - - // Light it up: - m_BlockLight[idx] = cBlockInfo::GetLightValue(m_BlockTypes[idx]); - } - } + DiscoverLightAdjacents(a_IndicesToProcess, a_Item, a_Light, Back); } } @@ -483,97 +329,55 @@ void cLightingThread::PrepareBlockLight2(void) -void cLightingThread::CalcLight(NIBBLETYPE * a_Light) +void cLightingThread::DiscoverLightAdjacents(std::vector<size_t> & a_IndicesToProcess, const cLightingChunkStay & a_Item, BlockDataArray & a_Light, size_t a_OriginatorIndex) { - size_t NumSeeds2 = 0; - while (m_NumSeeds > 0) - { - // Buffer 1 -> buffer 2 - memset(m_IsSeed2, 0, sizeof(m_IsSeed2)); - NumSeeds2 = 0; - CalcLightStep(a_Light, m_NumSeeds, m_IsSeed1, m_SeedIdx1, NumSeeds2, m_IsSeed2, m_SeedIdx2); - if (NumSeeds2 == 0) - { - return; - } + auto Position = cChunkDef::IndexToCoordinate(a_OriginatorIndex); - // Buffer 2 -> buffer 1 - memset(m_IsSeed1, 0, sizeof(m_IsSeed1)); - m_NumSeeds = 0; - CalcLightStep(a_Light, NumSeeds2, m_IsSeed2, m_SeedIdx2, m_NumSeeds, m_IsSeed1, m_SeedIdx1); + if (Position.x % cChunkDef::Width != 15) + { + // TODO: update neighbouring chunk + UpdateLightAdjacent(a_IndicesToProcess, a_Item, a_Light, a_OriginatorIndex, a_OriginatorIndex + 1); } -} - + if (Position.x % cChunkDef::Width != 0) + { + // TODO: update neighbouring chunk + UpdateLightAdjacent(a_IndicesToProcess, a_Item, a_Light, a_OriginatorIndex, a_OriginatorIndex - 1); + } + if (Position.z % cChunkDef::Width != 15) + { + // TODO: update neighbouring chunk + UpdateLightAdjacent(a_IndicesToProcess, a_Item, a_Light, a_OriginatorIndex, a_OriginatorIndex + cChunkDef::Width); + } + if (Position.z % cChunkDef::Width != 0) + { + // TODO: update neighbouring chunk + UpdateLightAdjacent(a_IndicesToProcess, a_Item, a_Light, a_OriginatorIndex, a_OriginatorIndex - cChunkDef::Width); + } -void cLightingThread::CalcLightStep( - NIBBLETYPE * a_Light, - size_t a_NumSeedsIn, unsigned char * a_IsSeedIn, unsigned int * a_SeedIdxIn, - size_t & a_NumSeedsOut, unsigned char * a_IsSeedOut, unsigned int * a_SeedIdxOut -) -{ - UNUSED(a_IsSeedIn); - size_t NumSeedsOut = 0; - for (size_t i = 0; i < a_NumSeedsIn; i++) + if (Position.y != cChunkDef::Height - 1) { - UInt32 SeedIdx = static_cast<UInt32>(a_SeedIdxIn[i]); - int SeedX = SeedIdx % (cChunkDef::Width * 3); - int SeedZ = (SeedIdx / (cChunkDef::Width * 3)) % (cChunkDef::Width * 3); - int SeedY = SeedIdx / BlocksPerYLayer; + UpdateLightAdjacent(a_IndicesToProcess, a_Item, a_Light, a_OriginatorIndex, a_OriginatorIndex + cChunkDef::Width * cChunkDef::Width); + } - // Propagate seed: - if (SeedX < cChunkDef::Width * 3 - 1) - { - PropagateLight(a_Light, SeedIdx, SeedIdx + 1, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut); - } - if (SeedX > 0) - { - PropagateLight(a_Light, SeedIdx, SeedIdx - 1, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut); - } - if (SeedZ < cChunkDef::Width * 3 - 1) - { - PropagateLight(a_Light, SeedIdx, SeedIdx + cChunkDef::Width * 3, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut); - } - if (SeedZ > 0) - { - PropagateLight(a_Light, SeedIdx, SeedIdx - cChunkDef::Width * 3, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut); - } - if (SeedY < cChunkDef::Height - 1) - { - PropagateLight(a_Light, SeedIdx, SeedIdx + cChunkDef::Width * cChunkDef::Width * 3 * 3, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut); - } - if (SeedY > 0) - { - PropagateLight(a_Light, SeedIdx, SeedIdx - cChunkDef::Width * cChunkDef::Width * 3 * 3, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut); - } - } // for i - a_SeedIdxIn[] - a_NumSeedsOut = NumSeedsOut; + if (Position.y != 0) + { + UpdateLightAdjacent(a_IndicesToProcess, a_Item, a_Light, a_OriginatorIndex, a_OriginatorIndex - cChunkDef::Width * cChunkDef::Width); + } } -void cLightingThread::CompressLight(NIBBLETYPE * a_LightArray, NIBBLETYPE * a_ChunkLight) +void cLightingThread::UpdateLightAdjacent(std::vector<size_t> & a_IndicesToProcess, const cLightingChunkStay & a_Item, BlockDataArray & a_Light, size_t a_OriginatorIndex, size_t a_DestinationIndex) { - int InIdx = cChunkDef::Width * 49; // Index to the first nibble of the middle chunk in the a_LightArray - int OutIdx = 0; - for (int y = 0; y < cChunkDef::Height; y++) + // Assertions for index validity occur within PropagateLightToAdjacent + if (PropagateLightToAdjacent(a_Item, a_Light, a_OriginatorIndex, a_DestinationIndex)) { - for (int z = 0; z < cChunkDef::Width; z++) - { - for (int x = 0; x < cChunkDef::Width; x += 2) - { - a_ChunkLight[OutIdx++] = static_cast<NIBBLETYPE>(a_LightArray[InIdx + 1] << 4) | a_LightArray[InIdx]; - InIdx += 2; - } - InIdx += cChunkDef::Width * 2; - } - // Skip into the next y-level in the 3x3 chunk blob; each level has cChunkDef::Width * 9 rows - // We've already walked cChunkDef::Width * 3 in the "for z" cycle, that makes cChunkDef::Width * 6 rows left to skip - InIdx += cChunkDef::Width * cChunkDef::Width * 6; + a_IndicesToProcess.emplace_back(a_DestinationIndex); } } @@ -581,15 +385,13 @@ void cLightingThread::CompressLight(NIBBLETYPE * a_LightArray, NIBBLETYPE * a_Ch -void cLightingThread::QueueChunkStay(cLightingChunkStay & a_ChunkStay) +void cLightingThread::CompressLight(const BlockDataArray & a_LightArray, BlockNibbles & a_ChunkLight) { - // Move the ChunkStay from the Pending queue to the lighting queue. + for (size_t Index = 0; Index != a_ChunkLight.size(); ++Index) { - cCSLock Lock(m_CS); - m_PendingQueue.remove(&a_ChunkStay); - m_Queue.push_back(&a_ChunkStay); + auto AdjustedIndex = Index * 2; + a_ChunkLight[Index] = a_LightArray[AdjustedIndex] | static_cast<NIBBLETYPE>(a_LightArray[AdjustedIndex + 1] << 4); } - m_evtItemAdded.Set(); } @@ -600,20 +402,13 @@ void cLightingThread::QueueChunkStay(cLightingChunkStay & a_ChunkStay) // cLightingThread::cLightingChunkStay: cLightingThread::cLightingChunkStay::cLightingChunkStay(cLightingThread & a_LightingThread, int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkCoordCallback> a_CallbackAfter) : - m_LightingThread(a_LightingThread), + m_CallbackAfter(std::move(a_CallbackAfter)), m_ChunkX(a_ChunkX), m_ChunkZ(a_ChunkZ), - m_CallbackAfter(std::move(a_CallbackAfter)) + m_MaxHeight(0), // Required by cReader to be initialised to zero + m_LightingThread(a_LightingThread) { - Add(a_ChunkX + 1, a_ChunkZ + 1); - Add(a_ChunkX + 1, a_ChunkZ); - Add(a_ChunkX + 1, a_ChunkZ - 1); - Add(a_ChunkX, a_ChunkZ + 1); - Add(a_ChunkX, a_ChunkZ); - Add(a_ChunkX, a_ChunkZ - 1); - Add(a_ChunkX - 1, a_ChunkZ + 1); - Add(a_ChunkX - 1, a_ChunkZ); - Add(a_ChunkX - 1, a_ChunkZ - 1); + Add(a_ChunkX, a_ChunkZ); } @@ -622,7 +417,43 @@ cLightingThread::cLightingChunkStay::cLightingChunkStay(cLightingThread & a_Ligh bool cLightingThread::cLightingChunkStay::OnAllChunksAvailable(void) { - m_LightingThread.QueueChunkStay(*this); + if (m_LightingThread.m_ShouldTerminate) + { + return false; + } + + ASSERT(m_LightingThread.m_World->IsChunkValid(m_ChunkX, m_ChunkZ)); + + // If the chunk is already lit, skip it: + if (m_LightingThread.m_World->IsChunkLighted(m_ChunkX, m_ChunkZ)) + { + if (m_CallbackAfter != nullptr) + { + m_CallbackAfter->Call(m_ChunkX, m_ChunkZ, true); + } + + + { + cCSLock Lock(m_LightingThread.m_CS); + m_LightingThread.m_PendingQueue.erase( + std::remove(m_LightingThread.m_PendingQueue.begin(), m_LightingThread.m_PendingQueue.end(), this), + m_LightingThread.m_PendingQueue.end() + ); + } + + return true; + } + + { + cCSLock Lock(m_LightingThread.m_CS); + m_LightingThread.m_PendingQueue.erase( + std::remove(m_LightingThread.m_PendingQueue.begin(), m_LightingThread.m_PendingQueue.end(), this), + m_LightingThread.m_PendingQueue.end() + ); + m_LightingThread.m_Queue.emplace_back(this); + } + + m_LightingThread.m_evtItemDataLoaded.Set(); // Keep the ChunkStay alive: return false; @@ -634,7 +465,7 @@ bool cLightingThread::cLightingChunkStay::OnAllChunksAvailable(void) void cLightingThread::cLightingChunkStay::OnDisabled(void) { - // Nothing needed in this callback + delete this; } diff --git a/src/LightingThread.h b/src/LightingThread.h index d95214a3c..45a18fdd9 100644 --- a/src/LightingThread.h +++ b/src/LightingThread.h @@ -51,34 +51,27 @@ class cLightingThread : { typedef cIsThread super; -public: - - cLightingThread(void); - ~cLightingThread(); - - bool Start(cWorld * a_World); - - void Stop(void); - - /** Queues the entire chunk for lighting. - The callback, if specified, is called after the lighting has been processed. */ - void QueueChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkCoordCallback> a_CallbackAfter); - - /** Blocks until the queue is empty or the thread is terminated */ - void WaitForQueueEmpty(void); +protected: - size_t GetQueueLength(void); + friend class cReader; -protected: + typedef std::array<BLOCKTYPE, cChunkDef::NumBlocks> BlockDataArray; + typedef std::array<HEIGHTTYPE, cChunkDef::Width * cChunkDef::Width> HeightDataArray; class cLightingChunkStay : public cChunkStay { public: - cLightingThread & m_LightingThread; + std::unique_ptr<cChunkCoordCallback> m_CallbackAfter; + int m_ChunkX; int m_ChunkZ; - std::unique_ptr<cChunkCoordCallback> m_CallbackAfter; + + /** The highest block in the current chunk data */ + HEIGHTTYPE m_MaxHeight; + + std::unique_ptr<BlockDataArray> m_BlockTypes; + std::unique_ptr<HeightDataArray> m_HeightMap; cLightingChunkStay(cLightingThread & a_LightingThread, int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkCoordCallback> a_CallbackAfter); @@ -90,108 +83,80 @@ protected: } virtual bool OnAllChunksAvailable(void) override; virtual void OnDisabled(void) override; - } ; - typedef std::list<cChunkStay *> cChunkStays; + private: + cLightingThread & m_LightingThread; + }; + typedef std::vector<cLightingChunkStay *> cChunkStays; + typedef std::array<NIBBLETYPE, cChunkDef::NumBlocks / 2> BlockNibbles; cWorld * m_World; /** The mutex to protect m_Queue and m_PendingQueue */ cCriticalSection m_CS; - /** The ChunkStays that are loaded and are waiting to be lit. */ - cChunkStays m_Queue; - /** The ChunkStays that are waiting for load. Used for stopping the thread. */ cChunkStays m_PendingQueue; - cEvent m_evtItemAdded; // Set when queue is appended, or to stop the thread - cEvent m_evtQueueEmpty; // Set when the queue gets empty - - /** The highest block in the current 3x3 chunk data */ - HEIGHTTYPE m_MaxHeight; - - - // Buffers for the 3x3 chunk data - // These buffers alone are 1.7 MiB in size, therefore they cannot be located on the stack safely - some architectures may have only 1 MiB for stack, or even less - // Placing the buffers into the object means that this object can light chunks only in one thread! - // The blobs are XZY organized as a whole, instead of 3x3 XZY-organized subarrays -> - // -> This means data has to be scatterred when reading and gathered when writing! - static const int BlocksPerYLayer = cChunkDef::Width * cChunkDef::Width * 3 * 3; - BLOCKTYPE m_BlockTypes[BlocksPerYLayer * cChunkDef::Height]; - NIBBLETYPE m_BlockLight[BlocksPerYLayer * cChunkDef::Height]; - NIBBLETYPE m_SkyLight [BlocksPerYLayer * cChunkDef::Height]; - HEIGHTTYPE m_HeightMap [BlocksPerYLayer]; - - // Seed management (5.7 MiB) - // Two buffers, in each calc step one is set as input and the other as output, then in the next step they're swapped - // Each seed is represented twice in this structure - both as a "list" and as a "position". - // "list" allows fast traversal from seed to seed - // "position" allows fast checking if a coord is already a seed - unsigned char m_IsSeed1 [BlocksPerYLayer * cChunkDef::Height]; - unsigned int m_SeedIdx1[BlocksPerYLayer * cChunkDef::Height]; - unsigned char m_IsSeed2 [BlocksPerYLayer * cChunkDef::Height]; - unsigned int m_SeedIdx2[BlocksPerYLayer * cChunkDef::Height]; - size_t m_NumSeeds; + /** The ChunkStays that are loaded. */ + cChunkStays m_Queue; + + cEvent m_evtItemDataLoaded; virtual void Execute(void) override; /** Lights the entire chunk. If neighbor chunks don't exist, touches them and re-queues the chunk */ void LightChunk(cLightingChunkStay & a_Item); - /** Prepares m_BlockTypes and m_HeightMap data; zeroes out the light arrays */ - void ReadChunks(int a_ChunkX, int a_ChunkZ); - /** Uses m_HeightMap to initialize the m_SkyLight[] data; fills in seeds for the skylight */ - void PrepareSkyLight(void); + std::vector<size_t> PrepareSkyLight(const cLightingChunkStay & a_Item, BlockDataArray & a_SkyLight); /** Uses m_BlockTypes to initialize the m_BlockLight[] data; fills in seeds for the blocklight */ - void PrepareBlockLight(void); - - /** Same as PrepareBlockLight(), but uses a different traversal scheme; possibly better perf cache-wise. - To be compared in perf benchmarks. */ - void PrepareBlockLight2(void); + std::vector<size_t> PrepareBlockLight(const cLightingChunkStay & a_Item, BlockDataArray & a_BlockLight); /** Calculates light in the light array specified, using stored seeds */ - void CalcLight(NIBBLETYPE * a_Light); + void CalcLight(const cLightingChunkStay & a_Item, std::vector<size_t> a_IndicesToProcess, BlockDataArray & a_Light); - /** Does one step in the light calculation - one seed propagation and seed recalculation */ - void CalcLightStep( - NIBBLETYPE * a_Light, - size_t a_NumSeedsIn, unsigned char * a_IsSeedIn, unsigned int * a_SeedIdxIn, - size_t & a_NumSeedsOut, unsigned char * a_IsSeedOut, unsigned int * a_SeedIdxOut - ); + void DiscoverLightAdjacents(std::vector<size_t> & a_IndicesToProcess, const cLightingChunkStay & a_Item, BlockDataArray & a_Light, size_t a_OriginatorIndex); - /** Compresses from 1-block-per-byte (faster calc) into 2-blocks-per-byte (MC storage): */ - void CompressLight(NIBBLETYPE * a_LightArray, NIBBLETYPE * a_ChunkLight); + /** Does one step in the light calculation - one seed propagation and seed recalculation */ + void UpdateLightAdjacent(std::vector<size_t> & a_IndicesToProcess, const cLightingChunkStay & a_Item, BlockDataArray & a_Light, size_t a_OriginatorIndex, size_t a_DestinationIndex); - inline void PropagateLight( - NIBBLETYPE * a_Light, - unsigned int a_SrcIdx, unsigned int a_DstIdx, - size_t & a_NumSeedsOut, unsigned char * a_IsSeedOut, unsigned int * a_SeedIdxOut - ) + inline bool PropagateLightToAdjacent(const cLightingChunkStay & a_Item, BlockDataArray & a_Light, size_t a_OriginatorIndex, size_t a_DestinationIndex) { - ASSERT(a_SrcIdx < ARRAYCOUNT(m_SkyLight)); - ASSERT(a_DstIdx < ARRAYCOUNT(m_BlockTypes)); + ASSERT(a_OriginatorIndex < a_Light.size()); + ASSERT(a_DestinationIndex < a_Light.size()); - if (a_Light[a_SrcIdx] <= a_Light[a_DstIdx] + cBlockInfo::GetSpreadLightFalloff(m_BlockTypes[a_DstIdx])) + auto Falloff = cBlockInfo::GetSpreadLightFalloff((*a_Item.m_BlockTypes)[a_DestinationIndex]); + if (a_Light[a_OriginatorIndex] <= a_Light[a_DestinationIndex] + Falloff) { // We're not offering more light than the dest block already has - return; + return false; } - a_Light[a_DstIdx] = a_Light[a_SrcIdx] - cBlockInfo::GetSpreadLightFalloff(m_BlockTypes[a_DstIdx]); - if (!a_IsSeedOut[a_DstIdx]) - { - a_IsSeedOut[a_DstIdx] = true; - a_SeedIdxOut[a_NumSeedsOut++] = a_DstIdx; - } + a_Light[a_DestinationIndex] = a_Light[a_OriginatorIndex] - Falloff; + return true; } - /** Queues a chunkstay that has all of its chunks loaded. - Called by cLightingChunkStay when all of its chunks are loaded. */ - void QueueChunkStay(cLightingChunkStay & a_ChunkStay); + /** Compresses from 1-block-per-byte (faster calc) into 2-blocks-per-byte (MC storage): */ + static void CompressLight(const BlockDataArray & a_LightArray, BlockNibbles & a_ChunkLight); + +public: + + cLightingThread(void); + ~cLightingThread(); + + bool Start(cWorld * a_World); + + void Stop(void); + + /** Queues the entire chunk for lighting + Takes a 3-by-3 chunks' worth of buffered data (blocktypes and heightmap) centered around the chunk to light + The callback, if specified, is called after the lighting has been processed. */ + void QueueChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkCoordCallback> a_CallbackAfter); + + size_t GetQueueLength(void); } ; diff --git a/src/MemorySettingsRepository.h b/src/MemorySettingsRepository.h index d452ec269..5d30f9682 100644 --- a/src/MemorySettingsRepository.h +++ b/src/MemorySettingsRepository.h @@ -53,29 +53,26 @@ private: struct sValue { - sValue(AString value): + sValue(AString value) : #ifdef _DEBUG m_Type(eType::String), #endif m_stringValue (value) - { - } + {} - sValue(Int64 value): + sValue(Int64 value) : #ifdef _DEBUG m_Type(eType::Int64), #endif m_intValue(value) - { - } + {} - sValue(bool value): + sValue(bool value) : #ifdef _DEBUG m_Type(eType::Bool), #endif m_boolValue(value) - { - } + {} AString getStringValue() const { ASSERT(m_Type == eType::String); return m_stringValue; } Int64 getIntValue() const { ASSERT(m_Type == eType::Int64); return m_intValue; } diff --git a/src/Mobs/Wolf.cpp b/src/Mobs/Wolf.cpp index da21468ca..ceaa41cfe 100644 --- a/src/Mobs/Wolf.cpp +++ b/src/Mobs/Wolf.cpp @@ -361,7 +361,7 @@ void cWolf::TickFollowPlayer() if (!Callback.OwnerFlying) { Callback.OwnerPos.y = FindFirstNonAirBlockPosition(Callback.OwnerPos.x, Callback.OwnerPos.z); - TeleportToCoords(Callback.OwnerPos.x, Callback.OwnerPos.y, Callback.OwnerPos.z); + TeleportToCoords(Callback.OwnerPos); SetTarget(nullptr); } } diff --git a/src/NetherPortalScanner.cpp b/src/NetherPortalScanner.cpp index cebf09ceb..0d1124060 100644 --- a/src/NetherPortalScanner.cpp +++ b/src/NetherPortalScanner.cpp @@ -10,8 +10,9 @@ -cNetherPortalScanner::cNetherPortalScanner(cEntity * a_MovingEntity, cWorld * a_DestinationWorld, Vector3d a_DestPosition, int a_MaxY) : - m_Entity(a_MovingEntity), +cNetherPortalScanner::cNetherPortalScanner(UInt32 a_MovingEntityID, eDimension a_PreviousDimension, cWorld * a_DestinationWorld, Vector3d a_DestPosition, int a_MaxY) : + m_EntityID(a_MovingEntityID), + m_PreviousDimension(a_PreviousDimension), m_World(a_DestinationWorld), m_FoundPortal(false), m_BuildPlatform(true), @@ -31,7 +32,13 @@ cNetherPortalScanner::cNetherPortalScanner(cEntity * a_MovingEntity, cWorld * a_ Add(x, z); } } - Enable(*a_DestinationWorld->GetChunkMap()); + + a_DestinationWorld->QueueTask( + [this](cWorld & a_World) + { + Enable(*a_World.GetChunkMap()); + } + ); } @@ -289,8 +296,38 @@ void cNetherPortalScanner::OnDisabled(void) Position.z += OutOffset; } - LOGD("Placing player at {%f, %f, %f}", Position.x, Position.y, Position.z); - m_Entity->ScheduleMoveToWorld(m_World, Position, true); + Position.y++; + + class cCallback : public cEntityCallback + { + public: + cCallback(const eDimension & a_PreviousDimension, const Vector3d & a_Position) : + m_PreviousDimension(a_PreviousDimension), + m_Position(a_Position) + { + } + + virtual bool Item(cEntity * a_Entity) override + { + a_Entity->OnPostWorldTravel(m_PreviousDimension, m_Position); + return true; + } + + private: + const eDimension & m_PreviousDimension; + const Vector3d & m_Position; + }; + + m_World->QueueTask( + [PreviousDimension = m_PreviousDimension, Position, EntityID = m_EntityID](cWorld & a_World) + { + LOGD("Placing player at {%f, %f, %f}", Position.x, Position.y, Position.z); + + cCallback CB(PreviousDimension, Position); + a_World.DoWithEntityByID(EntityID, CB); + } + ); + delete this; } diff --git a/src/NetherPortalScanner.h b/src/NetherPortalScanner.h index e9e210b2f..d6ed6326d 100644 --- a/src/NetherPortalScanner.h +++ b/src/NetherPortalScanner.h @@ -16,7 +16,7 @@ class cWorld; class cNetherPortalScanner : public cChunkStay { public: - cNetherPortalScanner(cEntity * a_MovingEntity, cWorld * a_DestinationWorld, Vector3d a_DestPosition, int a_MaxY); + cNetherPortalScanner(UInt32 a_MovingEntityID, eDimension a_PreviousDimension, cWorld * a_DestinationWorld, Vector3d a_DestPosition, int a_MaxY); virtual void OnChunkAvailable(int a_ChunkX, int a_ChunkY) override; virtual bool OnAllChunksAvailable(void) override; virtual void OnDisabled(void) override; @@ -49,8 +49,11 @@ private: /** Whether the given location is a valid location to build a portal. */ bool IsValidBuildLocation(Vector3i a_BlockPosition); - /** The entity that's being moved. */ - cEntity * m_Entity; + /** The ID of the entity that's being moved. */ + UInt32 m_EntityID; + + /** The dimension the entity was originally in. */ + eDimension m_PreviousDimension; /** The world we're moving the entity to. */ cWorld * m_World; diff --git a/src/OSSupport/Event.cpp b/src/OSSupport/Event.cpp index be2803451..02f1fdbcc 100644 --- a/src/OSSupport/Event.cpp +++ b/src/OSSupport/Event.cpp @@ -62,16 +62,3 @@ void cEvent::Set(void) -void cEvent::SetAll(void) -{ - { - std::unique_lock<std::mutex> Lock(m_Mutex); - m_ShouldContinue = true; - } - m_CondVar.notify_all(); -} - - - - - diff --git a/src/OSSupport/Event.h b/src/OSSupport/Event.h index 94f062123..44da0c97b 100644 --- a/src/OSSupport/Event.h +++ b/src/OSSupport/Event.h @@ -30,10 +30,6 @@ public: If there was no thread waiting, the next call to Wait() will not block. */ void Set(void); - /** Sets the event - releases all threads that have been waiting in Wait(). - If there was no thread waiting, the next call to Wait() will not block. */ - void SetAll(void); - /** Waits for the event until either it is signalled, or the (relative) timeout is passed. Returns true if the event was signalled, false if the timeout was hit or there was an error. */ bool Wait(unsigned a_TimeoutMSec); diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp index b15b6282f..0432ca973 100644 --- a/src/OSSupport/TCPLinkImpl.cpp +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -88,8 +88,9 @@ cTCPLinkImplPtr cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cTC // Success return res; } + // Failure - cNetworkSingleton::Get().RemoveLink(res.get()); + res->Close(); return nullptr; } @@ -99,8 +100,9 @@ cTCPLinkImplPtr cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cTC // Success return res; } + // Failure - cNetworkSingleton::Get().RemoveLink(res.get()); + res->Close(); return nullptr; } @@ -214,6 +216,7 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void { ASSERT(a_Self != nullptr); cTCPLinkImplPtr Self = static_cast<cTCPLinkImpl *>(a_Self)->m_Self; + ASSERT(Self != nullptr); // If an error is reported, call the error callback: if (a_What & BEV_EVENT_ERROR) @@ -232,16 +235,9 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void else { Self->m_Callbacks->OnError(err, evutil_socket_error_to_string(err)); - if (Self->m_Server == nullptr) - { - cNetworkSingleton::Get().RemoveLink(Self.get()); - } - else - { - Self->m_Server->RemoveLink(Self.get()); - } } - Self->m_Self.reset(); + + Self->Close(); return; } @@ -263,15 +259,7 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void if (a_What & BEV_EVENT_EOF) { Self->m_Callbacks->OnRemoteClosed(); - if (Self->m_Server != nullptr) - { - Self->m_Server->RemoveLink(Self.get()); - } - else - { - cNetworkSingleton::Get().RemoveLink(Self.get()); - } - Self->m_Self.reset(); + Self->Close(); return; } diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp index ae9571f03..9d692b35e 100644 --- a/src/Protocol/Protocol18x.cpp +++ b/src/Protocol/Protocol18x.cpp @@ -616,7 +616,7 @@ void cProtocol180::SendLogin(const cPlayer & a_Player, const cWorld & a_World) { // Send the Join Game packet: { - cServer * Server = cRoot::Get()->GetServer(); + auto Server = cRoot::Get()->GetServer(); cPacketizer Pkt(*this, 0x01); // Join Game packet Pkt.WriteBEUInt32(a_Player.GetUniqueID()); Pkt.WriteBEUInt8(static_cast<UInt8>(a_Player.GetEffectiveGameMode()) | (Server->IsHardcore() ? 0x08 : 0)); // Hardcore flag bit 4 @@ -926,15 +926,11 @@ void cProtocol180::SendPlayerListUpdatePing(const cPlayer & a_Player) { ASSERT(m_State == 3); // In game mode? - auto ClientHandle = a_Player.GetClientHandlePtr(); - if (ClientHandle != nullptr) - { - cPacketizer Pkt(*this, 0x38); // Playerlist Item packet - Pkt.WriteVarInt32(2); - Pkt.WriteVarInt32(1); - Pkt.WriteUUID(a_Player.GetUUID()); - Pkt.WriteVarInt32(static_cast<UInt32>(ClientHandle->GetPing())); - } + cPacketizer Pkt(*this, 0x38); // Playerlist Item packet + Pkt.WriteVarInt32(2); + Pkt.WriteVarInt32(1); + Pkt.WriteUUID(a_Player.GetUUID()); + Pkt.WriteVarInt32(static_cast<UInt32>(a_Player.GetClientHandle()->GetPing())); } @@ -2236,7 +2232,7 @@ void cProtocol180::HandlePacketLoginStart(cByteBuffer & a_ByteBuffer) void cProtocol180::HandlePacketAnimation(cByteBuffer & a_ByteBuffer) { - m_Client->HandleAnimation(0); // Packet exists solely for arm-swing notification + m_Client->QueueDataCommit(&cClientHandle::HandleAnimation, m_Client, 0); // Packet exists solely for arm-swing notification } @@ -2245,6 +2241,11 @@ void cProtocol180::HandlePacketAnimation(cByteBuffer & a_ByteBuffer) void cProtocol180::HandlePacketBlockDig(cByteBuffer & a_ByteBuffer) { + if (!m_Client->EnforceBlockInteractionsRate()) + { + return; + } + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Status); int BlockX, BlockY, BlockZ; @@ -2254,7 +2255,7 @@ void cProtocol180::HandlePacketBlockDig(cByteBuffer & a_ByteBuffer) } HANDLE_READ(a_ByteBuffer, ReadBEInt8, Int8, Face); - m_Client->HandleLeftClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), Status); + m_Client->QueueDataCommit(&cClientHandle::HandleLeftClick, m_Client, BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), Status); } @@ -2263,6 +2264,11 @@ void cProtocol180::HandlePacketBlockDig(cByteBuffer & a_ByteBuffer) void cProtocol180::HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer) { + if (!m_Client->EnforceBlockInteractionsRate()) + { + return; + } + int BlockX, BlockY, BlockZ; if (!a_ByteBuffer.ReadPosition64(BlockX, BlockY, BlockZ)) { @@ -2277,7 +2283,7 @@ void cProtocol180::HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorX); HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorY); HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorZ); - m_Client->HandleRightClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), CursorX, CursorY, CursorZ, m_Client->GetPlayer()->GetEquippedItem()); + m_Client->QueueDataCommit(&cClientHandle::HandleRightClick, m_Client, BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), CursorX, CursorY, CursorZ, Item); } @@ -2287,7 +2293,7 @@ void cProtocol180::HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer) void cProtocol180::HandlePacketChatMessage(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Message); - m_Client->HandleChat(Message); + m_Client->QueueDataCommit(&cClientHandle::HandleChat, m_Client, Message); } @@ -2314,26 +2320,22 @@ void cProtocol180::HandlePacketClientSettings(cByteBuffer & a_ByteBuffer) void cProtocol180::HandlePacketClientStatus(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, ActionID); + switch (ActionID) { case 0: - { - // Respawn - m_Client->HandleRespawn(); + { // Respawn + m_Client->QueueDataCommit(&cClientHandle::HandleRespawn, m_Client); break; } case 1: - { - // Request stats - const cStatManager & Manager = m_Client->GetPlayer()->GetStatManager(); - SendStatistics(Manager); - + { // Request stats + m_Client->QueueDataCommit(&cClientHandle::SendStatistics, m_Client, m_Client->GetPlayer()->GetStatManager()); break; } case 2: - { - // Open Inventory achievement - m_Client->GetPlayer()->AwardAchievement(achOpenInv); + { // Open Inventory achievement + m_Client->QueueDataCommit(&cPlayer::AwardAchievement, m_Client->GetPlayer(), achOpenInv); break; } } @@ -2351,7 +2353,7 @@ void cProtocol180::HandlePacketCreativeInventoryAction(cByteBuffer & a_ByteBuffe { return; } - m_Client->HandleCreativeInventory(SlotNum, Item, (SlotNum == -1) ? caLeftClickOutside : caLeftClick); + m_Client->QueueDataCommit(&cClientHandle::HandleCreativeInventory, m_Client, SlotNum, Item, (SlotNum == -1) ? caLeftClickOutside : caLeftClick); } @@ -2366,11 +2368,11 @@ void cProtocol180::HandlePacketEntityAction(cByteBuffer & a_ByteBuffer) switch (Action) { - case 0: m_Client->HandleEntityCrouch(PlayerID, true); break; // Crouch - case 1: m_Client->HandleEntityCrouch(PlayerID, false); break; // Uncrouch - case 2: m_Client->HandleEntityLeaveBed(PlayerID); break; // Leave Bed - case 3: m_Client->HandleEntitySprinting(PlayerID, true); break; // Start sprinting - case 4: m_Client->HandleEntitySprinting(PlayerID, false); break; // Stop sprinting + case 0: m_Client->QueueDataCommit(&cClientHandle::HandleEntityCrouch, m_Client, PlayerID, true); break; // Crouch + case 1: m_Client->QueueDataCommit(&cClientHandle::HandleEntityCrouch, m_Client, PlayerID, false); break; // Uncrouch + case 2: m_Client->QueueDataCommit(&cClientHandle::HandleEntityLeaveBed, m_Client, PlayerID); break; // Leave Bed + case 3: m_Client->QueueDataCommit(&cClientHandle::HandleEntitySprinting, m_Client, PlayerID, true); break; // Start sprinting + case 4: m_Client->QueueDataCommit(&cClientHandle::HandleEntitySprinting, m_Client, PlayerID, false); break; // Stop sprinting } } @@ -2415,7 +2417,7 @@ void cProtocol180::HandlePacketPlayerAbilities(cByteBuffer & a_ByteBuffer) CanFly = true; } - m_Client->HandlePlayerAbilities(CanFly, IsFlying, FlyingSpeed, WalkingSpeed); + m_Client->QueueDataCommit(&cClientHandle::HandlePlayerAbilities, m_Client, CanFly, IsFlying, FlyingSpeed, WalkingSpeed); } @@ -2427,7 +2429,7 @@ void cProtocol180::HandlePacketPlayerLook(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Yaw); HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Pitch); HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); - m_Client->HandlePlayerLook(Yaw, Pitch, IsOnGround); + m_Client->QueueDataCommit(&cClientHandle::HandlePlayerLook, m_Client, Yaw, Pitch, IsOnGround); } @@ -2440,7 +2442,7 @@ void cProtocol180::HandlePacketPlayerPos(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, PosY); HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, PosZ); HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); - m_Client->HandlePlayerPos(PosX, PosY, PosZ, PosY + (m_Client->GetPlayer()->IsCrouched() ? 1.54 : 1.62), IsOnGround); + m_Client->QueueDataCommit(&cClientHandle::HandlePlayerPos, m_Client, PosX, PosY, PosZ, PosY + (m_Client->GetPlayer()->IsCrouched() ? 1.54 : 1.62), IsOnGround); } @@ -2455,7 +2457,7 @@ void cProtocol180::HandlePacketPlayerPosLook(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Yaw); HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Pitch); HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); - m_Client->HandlePlayerMoveLook(PosX, PosY, PosZ, PosY + 1.62, Yaw, Pitch, IsOnGround); + m_Client->QueueDataCommit(&cClientHandle::HandlePlayerMoveLook, m_Client, PosX, PosY, PosZ, PosY + 1.62, Yaw, Pitch, IsOnGround); } @@ -2496,7 +2498,7 @@ void cProtocol180::HandlePacketPluginMessage(cByteBuffer & a_ByteBuffer) void cProtocol180::HandlePacketSlotSelect(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadBEInt16, Int16, SlotNum); - m_Client->HandleSlotSelected(SlotNum); + m_Client->QueueDataCommit(&cClientHandle::HandleSlotSelected, m_Client, SlotNum); } @@ -2511,7 +2513,7 @@ void cProtocol180::HandlePacketSteerVehicle(cByteBuffer & a_ByteBuffer) if ((Flags & 0x2) != 0) { - m_Client->HandleUnmount(); + m_Client->QueueDataCommit(&cClientHandle::HandleUnmount, m_Client); } else if ((Flags & 0x1) != 0) { @@ -2519,7 +2521,7 @@ void cProtocol180::HandlePacketSteerVehicle(cByteBuffer & a_ByteBuffer) } else { - m_Client->HandleSteerVehicle(Forward, Sideways); + m_Client->QueueDataCommit(&cClientHandle::HandleSteerVehicle, m_Client, Forward, Sideways); } } @@ -2537,7 +2539,7 @@ void cProtocol180::HandlePacketTabComplete(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEInt64, Int64, Position); } - m_Client->HandleTabCompletion(Text); + m_Client->QueueDataCommit(&cClientHandle::HandleTabCompletion, m_Client, Text); } @@ -2580,12 +2582,12 @@ void cProtocol180::HandlePacketUseEntity(cByteBuffer & a_ByteBuffer) { case 0: { - m_Client->HandleUseEntity(EntityID, false); + m_Client->QueueDataCommit(&cClientHandle::HandleUseEntity, m_Client, EntityID, false); break; } case 1: { - m_Client->HandleUseEntity(EntityID, true); + m_Client->QueueDataCommit(&cClientHandle::HandleUseEntity, m_Client, EntityID, true); break; } case 2: @@ -2614,7 +2616,7 @@ void cProtocol180::HandlePacketEnchantItem(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Enchantment); - m_Client->HandleEnchantItem(WindowID, Enchantment); + m_Client->QueueDataCommit(&cClientHandle::HandleEnchantItem, m_Client, WindowID, Enchantment); } @@ -2666,7 +2668,7 @@ void cProtocol180::HandlePacketWindowClick(cByteBuffer & a_ByteBuffer) } } - m_Client->HandleWindowClick(WindowID, SlotNum, Action, Item); + m_Client->QueueDataCommit(&cClientHandle::HandleWindowClick, m_Client, WindowID, SlotNum, Action, Item); } @@ -2676,7 +2678,7 @@ void cProtocol180::HandlePacketWindowClick(cByteBuffer & a_ByteBuffer) void cProtocol180::HandlePacketWindowClose(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); - m_Client->HandleWindowClose(WindowID); + m_Client->QueueDataCommit(&cClientHandle::HandleWindowClose, m_Client, WindowID); } @@ -2696,13 +2698,20 @@ void cProtocol180::HandleVanillaPluginMessage(cByteBuffer & a_ByteBuffer, const HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, BlockY); HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, BlockZ); HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Command); - m_Client->HandleCommandBlockBlockChange(BlockX, BlockY, BlockZ, Command); + m_Client->QueueDataCommit(&cClientHandle::HandleCommandBlockBlockChange, m_Client, BlockX, BlockY, BlockZ, Command); break; } default: { - m_Client->SendChat(Printf("Failure setting command block command; unhandled mode %u (0x%02x)", Mode, Mode), mtFailure); + m_Client->QueueDataCommit( + static_cast<void(cClientHandle::*)(const AString &, eMessageType, const AString &)>(&cClientHandle::SendChat), + m_Client, + Printf("Failure setting command block command; unhandled mode %u (0x%02x)", Mode, Mode), + mtFailure, + "" + ); + LOG("Unhandled MC|AdvCdm packet mode."); return; } @@ -2721,19 +2730,19 @@ void cProtocol180::HandleVanillaPluginMessage(cByteBuffer & a_ByteBuffer, const { HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, Effect1); HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, Effect2); - m_Client->HandleBeaconSelection(Effect1, Effect2); + m_Client->QueueDataCommit(&cClientHandle::HandleBeaconSelection, m_Client, Effect1, Effect2); return; } else if (a_Channel == "MC|ItemName") { HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, ItemName); - m_Client->HandleAnvilItemName(ItemName); + m_Client->QueueDataCommit(&cClientHandle::HandleAnvilItemName, m_Client, ItemName); return; } else if (a_Channel == "MC|TrSel") { HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, SlotNum); - m_Client->HandleNPCTrade(SlotNum); + m_Client->QueueDataCommit(&cClientHandle::HandleNPCTrade, m_Client, SlotNum); return; } LOG("Unhandled vanilla plugin channel: \"%s\".", a_Channel.c_str()); @@ -2890,7 +2899,7 @@ void cProtocol180::StartEncryption(const Byte * a_Key) // Prepare the m_AuthServerID: cSha1Checksum Checksum; - cServer * Server = cRoot::Get()->GetServer(); + auto Server = cRoot::Get()->GetServer(); const AString & ServerID = Server->GetServerID(); Checksum.Update(reinterpret_cast<const Byte *>(ServerID.c_str()), ServerID.length()); Checksum.Update(a_Key, 16); diff --git a/src/Protocol/Protocol18x.h b/src/Protocol/Protocol18x.h index 08a51f342..52fb4eb52 100644 --- a/src/Protocol/Protocol18x.h +++ b/src/Protocol/Protocol18x.h @@ -234,8 +234,6 @@ protected: /** Sends the packet to the client. Called by the cPacketizer's destructor. */ virtual void SendPacket(cPacketizer & a_Packet) override; - void SendCompass(const cWorld & a_World); - /** Reads an item out of the received data, sets a_Item to the values read. Returns false if not enough received data. a_KeepRemainingBytes tells the function to keep that many bytes at the end of the buffer. */ diff --git a/src/Protocol/Protocol19x.cpp b/src/Protocol/Protocol19x.cpp index c11e05565..84d522287 100644 --- a/src/Protocol/Protocol19x.cpp +++ b/src/Protocol/Protocol19x.cpp @@ -951,7 +951,7 @@ void cProtocol190::SendPlayerListUpdatePing(const cPlayer & a_Player) { ASSERT(m_State == 3); // In game mode? - auto ClientHandle = a_Player.GetClientHandlePtr(); + auto ClientHandle = a_Player.GetClientHandlePtr().lock(); if (ClientHandle != nullptr) { cPacketizer Pkt(*this, 0x2d); // Playerlist Item packet @@ -2274,7 +2274,7 @@ void cProtocol190::HandlePacketAnimation(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadVarInt, Int32, Hand); - m_Client->HandleAnimation(0); // Packet exists solely for arm-swing notification + m_Client->QueueDataCommit(&cClientHandle::HandleAnimation, m_Client, 0); // Packet exists solely for arm-swing notification } @@ -2292,7 +2292,7 @@ void cProtocol190::HandlePacketBlockDig(cByteBuffer & a_ByteBuffer) } HANDLE_READ(a_ByteBuffer, ReadVarInt, Int32, Face); - m_Client->HandleLeftClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), Status); + m_Client->QueueDataCommit(&cClientHandle::HandleLeftClick, m_Client, BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), Status); } @@ -2312,7 +2312,13 @@ void cProtocol190::HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorX); HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorY); HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorZ); - m_Client->HandleRightClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), CursorX, CursorY, CursorZ, m_Client->GetPlayer()->GetEquippedItem()); + + m_Client->QueueDataCommit( + [Client = m_Client, BlockX, BlockY, BlockZ, Face, CursorX, CursorY, CursorZ] + { + Client->HandleRightClick(BlockX, BlockY, BlockZ, cProtocol190::FaceIntToBlockFace(Face), CursorX, CursorY, CursorZ, Client->GetPlayer()->GetEquippedItem()); + } + ); } @@ -2324,18 +2330,24 @@ void cProtocol190::HandlePacketBoatSteer(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBool, bool, RightPaddle); HANDLE_READ(a_ByteBuffer, ReadBool, bool, LeftPaddle); - // Get the players vehicle - cPlayer * Player = m_Client->GetPlayer(); - cEntity * Vehicle = Player->GetAttached(); - - if (Vehicle) - { - if (Vehicle->GetEntityType() == cEntity::etBoat) + m_Client->QueueDataCommit( + [Client = m_Client, RightPaddle, LeftPaddle] { - auto * Boat = reinterpret_cast<cBoat *>(Vehicle); - Boat->UpdatePaddles(RightPaddle, LeftPaddle); + // Get the players vehicle + auto Player = Client->GetPlayer(); + auto Vehicle = Player->GetAttached(); + + if (Vehicle == nullptr) + { + return; + } + + if (Vehicle->GetEntityType() == cEntity::etBoat) + { + reinterpret_cast<cBoat *>(Vehicle)->UpdatePaddles(RightPaddle, LeftPaddle); + } } - } + ); } @@ -2345,7 +2357,7 @@ void cProtocol190::HandlePacketBoatSteer(cByteBuffer & a_ByteBuffer) void cProtocol190::HandlePacketChatMessage(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Message); - m_Client->HandleChat(Message); + m_Client->QueueDataCommit(&cClientHandle::HandleChat, m_Client, Message); } @@ -2378,21 +2390,25 @@ void cProtocol190::HandlePacketClientStatus(cByteBuffer & a_ByteBuffer) case 0: { // Respawn - m_Client->HandleRespawn(); + m_Client->QueueDataCommit(&cClientHandle::HandleRespawn, m_Client); break; } case 1: { // Request stats - const cStatManager & Manager = m_Client->GetPlayer()->GetStatManager(); - SendStatistics(Manager); + m_Client->QueueDataCommit( + [Client = m_Client] + { + Client->GetProtocol().SendStatistics(Client->GetPlayer()->GetStatManager()); + } + ); break; } case 2: { // Open Inventory achievement - m_Client->GetPlayer()->AwardAchievement(achOpenInv); + m_Client->QueueDataCommit(&cPlayer::AwardAchievement, m_Client->GetPlayer(), achOpenInv); break; } } @@ -2420,7 +2436,7 @@ void cProtocol190::HandlePacketCreativeInventoryAction(cByteBuffer & a_ByteBuffe { return; } - m_Client->HandleCreativeInventory(SlotNum, Item, (SlotNum == -1) ? caLeftClickOutside : caLeftClick); + m_Client->QueueDataCommit(&cClientHandle::HandleCreativeInventory, m_Client, SlotNum, Item, (SlotNum == -1) ? caLeftClickOutside : caLeftClick); } @@ -2435,11 +2451,11 @@ void cProtocol190::HandlePacketEntityAction(cByteBuffer & a_ByteBuffer) switch (Action) { - case 0: m_Client->HandleEntityCrouch(PlayerID, true); break; // Crouch - case 1: m_Client->HandleEntityCrouch(PlayerID, false); break; // Uncrouch - case 2: m_Client->HandleEntityLeaveBed(PlayerID); break; // Leave Bed - case 3: m_Client->HandleEntitySprinting(PlayerID, true); break; // Start sprinting - case 4: m_Client->HandleEntitySprinting(PlayerID, false); break; // Stop sprinting + case 0: m_Client->QueueDataCommit(&cClientHandle::HandleEntityCrouch, m_Client, PlayerID, true); break; // Crouch + case 1: m_Client->QueueDataCommit(&cClientHandle::HandleEntityCrouch, m_Client, PlayerID, false); break; // Uncrouch + case 2: m_Client->QueueDataCommit(&cClientHandle::HandleEntityLeaveBed, m_Client, PlayerID); break; // Leave Bed + case 3: m_Client->QueueDataCommit(&cClientHandle::HandleEntitySprinting, m_Client, PlayerID, true); break; // Start sprinting + case 4: m_Client->QueueDataCommit(&cClientHandle::HandleEntitySprinting, m_Client, PlayerID, false); break; // Stop sprinting } } @@ -2460,7 +2476,7 @@ void cProtocol190::HandlePacketKeepAlive(cByteBuffer & a_ByteBuffer) void cProtocol190::HandlePacketPlayer(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); - // TODO: m_Client->HandlePlayerOnGround(IsOnGround); + // TODO: m_Client->QueueDataCommit(&cClientHandle::HandlePlayerOnGround, m_Client, IsOnGround); } @@ -2484,7 +2500,7 @@ void cProtocol190::HandlePacketPlayerAbilities(cByteBuffer & a_ByteBuffer) CanFly = true; } - m_Client->HandlePlayerAbilities(CanFly, IsFlying, FlyingSpeed, WalkingSpeed); + m_Client->QueueDataCommit(&cClientHandle::HandlePlayerAbilities, m_Client, CanFly, IsFlying, FlyingSpeed, WalkingSpeed); } @@ -2496,7 +2512,7 @@ void cProtocol190::HandlePacketPlayerLook(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Yaw); HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Pitch); HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); - m_Client->HandlePlayerLook(Yaw, Pitch, IsOnGround); + m_Client->QueueDataCommit(&cClientHandle::HandlePlayerLook, m_Client, Yaw, Pitch, IsOnGround); } @@ -2509,7 +2525,12 @@ void cProtocol190::HandlePacketPlayerPos(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, PosY); HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, PosZ); HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); - m_Client->HandlePlayerPos(PosX, PosY, PosZ, PosY + (m_Client->GetPlayer()->IsCrouched() ? 1.54 : 1.62), IsOnGround); + m_Client->QueueDataCommit( + [Client = m_Client, PosX, PosY, PosZ, IsOnGround] + { + Client->HandlePlayerPos(PosX, PosY, PosZ, PosY + (Client->GetPlayer()->IsCrouched() ? 1.54 : 1.62), IsOnGround); + } + ); } @@ -2524,7 +2545,7 @@ void cProtocol190::HandlePacketPlayerPosLook(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Yaw); HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Pitch); HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); - m_Client->HandlePlayerMoveLook(PosX, PosY, PosZ, PosY + 1.62, Yaw, Pitch, IsOnGround); + m_Client->QueueDataCommit(&cClientHandle::HandlePlayerMoveLook, m_Client, PosX, PosY, PosZ, PosY + 1.62, Yaw, Pitch, IsOnGround); } @@ -2565,7 +2586,7 @@ void cProtocol190::HandlePacketPluginMessage(cByteBuffer & a_ByteBuffer) void cProtocol190::HandlePacketSlotSelect(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadBEInt16, Int16, SlotNum); - m_Client->HandleSlotSelected(SlotNum); + m_Client->QueueDataCommit(&cClientHandle::HandleSlotSelected, m_Client, SlotNum); } @@ -2580,7 +2601,7 @@ void cProtocol190::HandlePacketSteerVehicle(cByteBuffer & a_ByteBuffer) if ((Flags & 0x2) != 0) { - m_Client->HandleUnmount(); + m_Client->QueueDataCommit(&cClientHandle::HandleUnmount, m_Client); } else if ((Flags & 0x1) != 0) { @@ -2588,7 +2609,7 @@ void cProtocol190::HandlePacketSteerVehicle(cByteBuffer & a_ByteBuffer) } else { - m_Client->HandleSteerVehicle(Forward, Sideways); + m_Client->QueueDataCommit(&cClientHandle::HandleSteerVehicle, m_Client, Forward, Sideways); } } @@ -2607,7 +2628,7 @@ void cProtocol190::HandlePacketTabComplete(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEInt64, Int64, Position); } - m_Client->HandleTabCompletion(Text); + m_Client->QueueDataCommit(&cClientHandle::HandleTabCompletion, m_Client, Text); } @@ -2646,12 +2667,12 @@ void cProtocol190::HandlePacketUseEntity(cByteBuffer & a_ByteBuffer) case 0: { HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, Hand) - m_Client->HandleUseEntity(EntityID, false); + m_Client->QueueDataCommit(&cClientHandle::HandleUseEntity, m_Client, EntityID, false); break; } case 1: { - m_Client->HandleUseEntity(EntityID, true); + m_Client->QueueDataCommit(&cClientHandle::HandleUseEntity, m_Client, EntityID, true); break; } case 2: @@ -2680,8 +2701,13 @@ void cProtocol190::HandlePacketUseItem(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt64, Hand); - // Didn't click a block - emulate old values used with place block of -1, -1, -1 (and BLOCK_FACE_NONE). - m_Client->HandleRightClick(-1, 255, -1, BLOCK_FACE_NONE, 0, 0, 0, m_Client->GetPlayer()->GetEquippedItem()); + m_Client->QueueDataCommit( + [Client = m_Client] + { + // Didn't click a block - emulate old values used with place block of -1, -1, -1 (and BLOCK_FACE_NONE). + Client->HandleRightClick(-1, 255, -1, BLOCK_FACE_NONE, 0, 0, 0, Client->GetPlayer()->GetEquippedItem()); + } + ); } @@ -2693,7 +2719,7 @@ void cProtocol190::HandlePacketEnchantItem(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, Enchantment); - m_Client->HandleEnchantItem(WindowID, Enchantment); + m_Client->QueueDataCommit(&cClientHandle::HandleEnchantItem, m_Client, WindowID, Enchantment); } @@ -2709,17 +2735,23 @@ void cProtocol190::HandlePacketVehicleMove(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, yaw); HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, pitch); - // Get the players vehicle - cEntity * Vehicle = m_Client->GetPlayer()->GetAttached(); + m_Client->QueueDataCommit( + [Client = m_Client, xPos, yPos, zPos, yaw, pitch] + { + // Get the player's vehicle + auto Vehicle = Client->GetPlayer()->GetAttached(); + if (Vehicle == nullptr) + { + return; + } - if (Vehicle) - { - Vehicle->SetPosX(xPos); - Vehicle->SetPosY(yPos); - Vehicle->SetPosZ(zPos); - Vehicle->SetYaw(yaw); - Vehicle->SetPitch(pitch); - } + Vehicle->SetPosX(xPos); + Vehicle->SetPosY(yPos); + Vehicle->SetPosZ(zPos); + Vehicle->SetYaw(yaw); + Vehicle->SetPitch(pitch); + } + ); } @@ -2771,7 +2803,7 @@ void cProtocol190::HandlePacketWindowClick(cByteBuffer & a_ByteBuffer) } } - m_Client->HandleWindowClick(WindowID, SlotNum, Action, Item); + m_Client->QueueDataCommit(&cClientHandle::HandleWindowClick, m_Client, WindowID, SlotNum, Action, Item); } @@ -2781,7 +2813,7 @@ void cProtocol190::HandlePacketWindowClick(cByteBuffer & a_ByteBuffer) void cProtocol190::HandlePacketWindowClose(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); - m_Client->HandleWindowClose(WindowID); + m_Client->QueueDataCommit(&cClientHandle::HandleWindowClose, m_Client, WindowID); } @@ -2801,13 +2833,19 @@ void cProtocol190::HandleVanillaPluginMessage(cByteBuffer & a_ByteBuffer, const HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, BlockY); HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, BlockZ); HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, Command); - m_Client->HandleCommandBlockBlockChange(BlockX, BlockY, BlockZ, Command); + m_Client->QueueDataCommit(&cClientHandle::HandleCommandBlockBlockChange, m_Client, BlockX, BlockY, BlockZ, Command); break; } default: { - m_Client->SendChat(Printf("Failure setting command block command; unhandled mode %u (0x%02x)", Mode, Mode), mtFailure); + m_Client->QueueDataCommit( + static_cast<void(cClientHandle::*)(const AString &, eMessageType, const AString &)>(&cClientHandle::SendChat), + m_Client, + Printf("Failure setting command block command; unhandled mode %u (0x%02x)", Mode, Mode), + mtFailure, + "" + ); LOG("Unhandled MC|AdvCdm packet mode."); return; } @@ -2826,19 +2864,19 @@ void cProtocol190::HandleVanillaPluginMessage(cByteBuffer & a_ByteBuffer, const { HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, Effect1); HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, Effect2); - m_Client->HandleBeaconSelection(Effect1, Effect2); + m_Client->QueueDataCommit(&cClientHandle::HandleBeaconSelection, m_Client, Effect1, Effect2); return; } else if (a_Channel == "MC|ItemName") { HANDLE_READ(a_ByteBuffer, ReadVarUTF8String, AString, ItemName); - m_Client->HandleAnvilItemName(ItemName); + m_Client->QueueDataCommit(&cClientHandle::HandleAnvilItemName, m_Client, ItemName); return; } else if (a_Channel == "MC|TrSel") { HANDLE_READ(a_ByteBuffer, ReadBEInt32, Int32, SlotNum); - m_Client->HandleNPCTrade(SlotNum); + m_Client->QueueDataCommit(&cClientHandle::HandleNPCTrade, m_Client, SlotNum); return; } LOG("Unhandled vanilla plugin channel: \"%s\".", a_Channel.c_str()); diff --git a/src/Protocol/Protocol19x.h b/src/Protocol/Protocol19x.h index d23653702..7c4a88789 100644 --- a/src/Protocol/Protocol19x.h +++ b/src/Protocol/Protocol19x.h @@ -258,7 +258,7 @@ protected: /** Converts the BlockFace received by the protocol into eBlockFace constants. If the received value doesn't match any of our eBlockFace constants, BLOCK_FACE_NONE is returned. */ - eBlockFace FaceIntToBlockFace(UInt32 a_FaceInt); + static eBlockFace FaceIntToBlockFace(UInt32 a_FaceInt); /** Writes the item data into a packet. */ void WriteItem(cPacketizer & a_Pkt, const cItem & a_Item); diff --git a/src/Root.cpp b/src/Root.cpp index 7ce36f65b..3749aaf64 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -508,11 +508,9 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn void cRoot::StartWorlds(void) { - for (WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr) + for (const auto & World : m_WorldsByName) { - itr->second->Start(); - itr->second->InitializeSpawn(); - m_PluginManager->CallHookWorldStarted(*itr->second); + World.second->Start(); } } @@ -557,7 +555,7 @@ cWorld * cRoot::GetDefaultWorld() cWorld * cRoot::GetWorld(const AString & a_WorldName) { - WorldMap::iterator itr = m_WorldsByName.find(a_WorldName); + auto itr = m_WorldsByName.find(a_WorldName); if (itr != m_WorldsByName.end()) { return itr->second; @@ -572,15 +570,11 @@ cWorld * cRoot::GetWorld(const AString & a_WorldName) bool cRoot::ForEachWorld(cWorldListCallback & a_Callback) { - for (WorldMap::iterator itr = m_WorldsByName.begin(), itr2 = itr; itr != m_WorldsByName.end(); itr = itr2) + for (const auto & World : m_WorldsByName) { - ++itr2; - if (itr->second != nullptr) + if (a_Callback.Item(World.second)) { - if (a_Callback.Item(itr->second)) - { - return false; - } + return false; } } return true; @@ -674,9 +668,16 @@ void cRoot::AuthenticateUser(int a_ClientID, const AString & a_Name, const AStri int cRoot::GetTotalChunkCount(void) { int res = 0; - for (WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr) + for (const auto & World : m_WorldsByName) { - res += itr->second->GetNumChunks(); + int NumChunks; + World.second->QueueTask( + [&NumChunks](cWorld & a_World) + { + NumChunks = a_World.GetNumChunks(); + } + ).wait(); + res += NumChunks; } return res; } @@ -687,9 +688,9 @@ int cRoot::GetTotalChunkCount(void) void cRoot::SaveAllChunks(void) { - for (WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr) + for (const auto & World : m_WorldsByName) { - itr->second->QueueSaveAllChunks(); + World.second->QueueSaveAllChunks(); } } @@ -697,29 +698,29 @@ void cRoot::SaveAllChunks(void) void cRoot::SendPlayerLists(cPlayer * a_DestPlayer) { - for (const auto & itr : m_WorldsByName) + for (const auto & World : m_WorldsByName) { - itr.second->SendPlayerList(a_DestPlayer); - } // for itr - m_WorldsByName[] + World.second->SendPlayerList(a_DestPlayer); + } } void cRoot::BroadcastPlayerListsAddPlayer(const cPlayer & a_Player, const cClientHandle * a_Exclude) { - for (const auto & itr : m_WorldsByName) + for (const auto & World : m_WorldsByName) { - itr.second->BroadcastPlayerListAddPlayer(a_Player); - } // for itr - m_WorldsByName[] + World.second->BroadcastPlayerListAddPlayer(a_Player); + } } void cRoot::BroadcastChat(const AString & a_Message, eMessageType a_ChatPrefix) { - for (WorldMap::iterator itr = m_WorldsByName.begin(), end = m_WorldsByName.end(); itr != end; ++itr) + for (const auto & World : m_WorldsByName) { - itr->second->BroadcastChat(a_Message, nullptr, a_ChatPrefix); - } // for itr - m_WorldsByName[] + World.second->BroadcastChat(a_Message, nullptr, a_ChatPrefix); + } } @@ -728,20 +729,19 @@ void cRoot::BroadcastChat(const AString & a_Message, eMessageType a_ChatPrefix) void cRoot::BroadcastChat(const cCompositeChat & a_Message) { - for (WorldMap::iterator itr = m_WorldsByName.begin(), end = m_WorldsByName.end(); itr != end; ++itr) + for (const auto & World : m_WorldsByName) { - itr->second->BroadcastChat(a_Message); - } // for itr - m_WorldsByName[] + World.second->BroadcastChat(a_Message); + } } bool cRoot::ForEachPlayer(cPlayerListCallback & a_Callback) { - for (WorldMap::iterator itr = m_WorldsByName.begin(), itr2 = itr; itr != m_WorldsByName.end(); itr = itr2) + for (const auto & World : m_WorldsByName) { - ++itr2; - if (!itr->second->ForEachPlayer(a_Callback)) + if (!World.second->ForEachPlayer(a_Callback)) { return false; } @@ -808,9 +808,9 @@ bool cRoot::FindAndDoWithPlayer(const AString & a_PlayerName, cPlayerListCallbac bool cRoot::DoWithPlayerByUUID(const AString & a_PlayerUUID, cPlayerListCallback & a_Callback) { - for (WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr) + for (const auto & World : m_WorldsByName) { - if (itr->second->DoWithPlayerByUUID(a_PlayerUUID, a_Callback)) + if (World.second->DoWithPlayerByUUID(a_PlayerUUID, a_Callback)) { return true; } @@ -824,7 +824,7 @@ bool cRoot::DoWithPlayerByUUID(const AString & a_PlayerUUID, cPlayerListCallback bool cRoot::DoWithPlayer(const AString & a_PlayerName, cPlayerListCallback & a_Callback) { - for (auto World : m_WorldsByName) + for (const auto & World : m_WorldsByName) { if (World.second->DoWithPlayer(a_PlayerName, a_Callback)) { @@ -958,16 +958,23 @@ void cRoot::LogChunkStats(cCommandOutputCallback & a_Output) int SumNumInLighting = 0; int SumNumInGenerator = 0; int SumMem = 0; - for (WorldMap::iterator itr = m_WorldsByName.begin(), end = m_WorldsByName.end(); itr != end; ++itr) + for (const auto & WorldEntry : m_WorldsByName) { - cWorld * World = itr->second; + auto World = WorldEntry.second; int NumInGenerator = World->GetGeneratorQueueLength(); int NumInSaveQueue = static_cast<int>(World->GetStorageSaveQueueLength()); int NumInLoadQueue = static_cast<int>(World->GetStorageLoadQueueLength()); int NumValid = 0; int NumDirty = 0; int NumInLighting = 0; - World->GetChunkStats(NumValid, NumDirty, NumInLighting); + + World->QueueTask( + [&NumValid, &NumDirty, &NumInLighting](cWorld & a_World) + { + a_World.GetChunkStats(NumValid, NumDirty, NumInLighting); + } + ).wait(); + a_Output.Out("World %s:", World->GetName().c_str()); a_Output.Out(" Num loaded chunks: %d", NumValid); a_Output.Out(" Num dirty chunks: %d", NumDirty); diff --git a/src/Root.h b/src/Root.h index 722554332..b5c63774b 100644 --- a/src/Root.h +++ b/src/Root.h @@ -225,10 +225,10 @@ private: /** Loads the worlds from settings.ini, creates the worldmap */ void LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIniFile); - /** Starts each world's life */ + /** Starts the tick thread of each loaded world. */ void StartWorlds(void); - /** Stops each world's threads, so that it's safe to unload them */ + /** Stops each world's threads, so that it's safe to unload them. */ void StopWorlds(void); /** Unloads all worlds from memory */ diff --git a/src/Server.cpp b/src/Server.cpp index ba469bd3e..33d94e639 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -153,16 +153,6 @@ cServer::cServer(void) : -void cServer::ClientMovedToWorld(const cClientHandle * a_Client) -{ - cCSLock Lock(m_CSClients); - m_ClientsToRemove.push_back(const_cast<cClientHandle *>(a_Client)); -} - - - - - void cServer::PlayerCreated(const cPlayer * a_Player) { UNUSED(a_Player); @@ -300,10 +290,12 @@ void cServer::PrepareKeys(void) cTCPLink::cCallbacksPtr cServer::OnConnectionAccepted(const AString & a_RemoteIPAddress) { LOGD("Client \"%s\" connected!", a_RemoteIPAddress.c_str()); - cClientHandlePtr NewHandle = std::make_shared<cClientHandle>(a_RemoteIPAddress, m_ClientViewDistance); - NewHandle->SetSelf(NewHandle); - cCSLock Lock(m_CSClients); - m_Clients.push_back(NewHandle); + + auto NewHandle = std::make_shared<cClientHandle>(a_RemoteIPAddress, m_ClientViewDistance); + { + cCSLock Lock(m_CSClients); + m_Clients.push_back(NewHandle); + } return NewHandle; } @@ -330,9 +322,16 @@ bool cServer::Tick(float a_Dt) // Let the Root process all the queued commands: cRoot::Get()->TickCommands(); - // Tick all clients not yet assigned to a world: + // Tick all clients: TickClients(a_Dt); + // Process queued tasks: + TickQueuedTasks(); + + // cClientHandle::Destroy guarantees that no more data will be sent after it is called, and this may be from TickQueuedTasks, therefore, + // remove destroyed clients, after processing queued tasks: + ReleaseDestroyedClients(); + if (!m_bRestarting) { return true; @@ -351,41 +350,49 @@ bool cServer::Tick(float a_Dt) void cServer::TickClients(float a_Dt) { - cClientHandlePtrs RemoveClients; + cCSLock Lock(m_CSClients); + for (const auto & Client : m_Clients) { - cCSLock Lock(m_CSClients); + Client->Tick(a_Dt); + } +} - // Remove clients that have moved to a world (the world will be ticking them from now on) - for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr) - { - for (auto itrC = m_Clients.begin(), endC = m_Clients.end(); itrC != endC; ++itrC) - { - if (itrC->get() == *itr) - { - m_Clients.erase(itrC); - break; - } - } - } // for itr - m_ClientsToRemove[] - m_ClientsToRemove.clear(); - // Tick the remaining clients, take out those that have been destroyed into RemoveClients - for (auto itr = m_Clients.begin(); itr != m_Clients.end();) - { - if ((*itr)->IsDestroyed()) + + + +void cServer::ReleaseDestroyedClients(void) +{ + cCSLock Lock(m_CSClients); + m_Clients.erase( + std::remove_if( + m_Clients.begin(), + m_Clients.end(), + [](const decltype(m_Clients)::value_type & a_Client) { - // Delete the client later, when CS is not held, to avoid deadlock: https://forum.cuberite.org/thread-374.html - RemoveClients.push_back(*itr); - itr = m_Clients.erase(itr); - continue; + return a_Client->IsDestroyed(); } - (*itr)->ServerTick(a_Dt); - ++itr; - } // for itr - m_Clients[] + ), + m_Clients.end() + ); +} + + + + + +void cServer::TickQueuedTasks(void) +{ + decltype(m_Tasks) Tasks; + { + cCSLock Lock(m_CSTasks); + std::swap(Tasks, m_Tasks); } - // Delete the clients that have been destroyed - RemoveClients.clear(); + for (const auto & Task : Tasks) + { + Task(); + } } @@ -495,7 +502,7 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac { class WorldCallback : public cWorldListCallback { - virtual bool Item(cWorld * a_World) override + virtual bool Item(cWorld * a_EntityWorld) override { class EntityCallback : public cEntityCallback { @@ -507,8 +514,15 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac } return false; } - } EC; - a_World->ForEachEntity(EC); + }; + + a_EntityWorld->QueueTask( + [](cWorld & a_World) + { + EntityCallback EC; + a_World.ForEachEntity(EC); + } + ); return false; } } WC; @@ -646,6 +660,16 @@ void cServer::BindBuiltInConsoleCommands(void) void cServer::Shutdown(void) { + { + // Notify all clients of the shutdown: + cCSLock Lock(m_CSClients); + for (const auto & Client : m_Clients) + { + Client->StopPlayerDestructionManagement(); + // TODO: send "Server shut down: kthnxbai!" + } + } + // Stop listening on all sockets: for (auto srv: m_ServerHandles) { @@ -653,19 +677,14 @@ void cServer::Shutdown(void) } m_ServerHandles.clear(); + // At this point, OnRemoteClosed will eventually be called for the clienthandle before it is deleted + // No need to manually call Destroy + // Notify the tick thread and wait for it to terminate: m_bRestarting = true; m_RestartEvent.Wait(); cRoot::Get()->SaveAllChunks(); - - // Remove all clients: - cCSLock Lock(m_CSClients); - for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) - { - (*itr)->Destroy(); - } - m_Clients.clear(); } @@ -695,7 +714,7 @@ void cServer::AuthenticateUser(int a_ClientID, const AString & a_Name, const ASt { if ((*itr)->GetUniqueID() == a_ClientID) { - (*itr)->Authenticate(a_Name, a_UUID, a_Properties); + QueueTask(std::bind(&cClientHandle::Authenticate, *itr, a_Name, a_UUID, a_Properties)); return; } } // for itr - m_Clients[] diff --git a/src/Server.h b/src/Server.h index 600e7ca97..d36188baf 100644 --- a/src/Server.h +++ b/src/Server.h @@ -35,9 +35,6 @@ // fwd: class cPlayer; class cClientHandle; -typedef SharedPtr<cClientHandle> cClientHandlePtr; -typedef std::list<cClientHandlePtr> cClientHandlePtrs; -typedef std::list<cClientHandle *> cClientHandles; class cCommandOutputCallback; class cSettingsRepositoryInterface; @@ -97,6 +94,9 @@ public: void Shutdown(void); + /** Returns whether cServer::Shutdown has been called */ + bool IsShuttingDown(void) { return m_bRestarting; } + void KickUser(int a_ClientID, const AString & a_Reason); /** Authenticates the specified user, called by cAuthenticator */ @@ -104,12 +104,6 @@ public: const AString & GetServerID(void) const { return m_ServerID; } // tolua_export - /** Called by cClientHandle's destructor; stop m_SocketThreads from calling back into a_Client */ - void ClientDestroying(const cClientHandle * a_Client); - - /** Don't tick a_Client anymore, it will be ticked from its cPlayer instead */ - void ClientMovedToWorld(const cClientHandle * a_Client); - /** Notifies the server that a player was created; the server uses this to adjust the number of players */ void PlayerCreated(const cPlayer * a_Player); @@ -143,6 +137,21 @@ public: from the settings. */ bool ShouldAllowMultiWorldTabCompletion(void) const { return m_ShouldAllowMultiWorldTabCompletion; } + /** Queues a new task to be executed in the context of the server tick thread */ + template <typename FunctionType> + void QueueTask(FunctionType && a_Task) + { + cCSLock Lock(m_CSTasks); + m_Tasks.emplace_back(a_Task); + } + +#ifdef _DEBUG + + /** Debug-only function to ensure the current execution is within the context of the server tick thread */ + bool IsInTickThread(void) { return m_TickThread.IsCurrentThread(); } + +#endif + private: friend class cRoot; // so cRoot can create and destroy cServer @@ -172,10 +181,7 @@ private: cCriticalSection m_CSClients; /** Clients that are connected to the server and not yet assigned to a cWorld. */ - cClientHandlePtrs m_Clients; - - /** Clients that have just been moved into a world and are to be removed from m_Clients in the next Tick(). */ - cClientHandles m_ClientsToRemove; + std::vector<std::shared_ptr<cClientHandle>> m_Clients; /** Protects m_PlayerCount against multithreaded access. */ mutable cCriticalSection m_CSPlayerCount; @@ -241,6 +247,12 @@ private: Initialized in InitServer(), used in Start(). */ AStringVector m_Ports; + /** Enforces thread safety for member variable m_Tasks */ + cCriticalSection m_CSTasks; + + /** Stores tasks queued onto the server tick thread to be executed as soon as possible with tick resolution */ + std::vector<std::function<void(void)>> m_Tasks; + cServer(void); @@ -255,6 +267,12 @@ private: /** Ticks the clients in m_Clients, manages the list in respect to removing clients */ void TickClients(float a_Dt); + + /** Removes all clienthandles who return true as a response to a call to IsDestroyed() */ + void ReleaseDestroyedClients(void); + + /** Processes tasks queued in m_Tasks within the server tick thread */ + void TickQueuedTasks(void); }; // tolua_export diff --git a/src/SetChunkData.cpp b/src/SetChunkData.cpp index d85f78459..f4b7ff504 100644 --- a/src/SetChunkData.cpp +++ b/src/SetChunkData.cpp @@ -6,6 +6,7 @@ #include "Globals.h" #include "SetChunkData.h" #include "BlockEntities/BlockEntity.h" +#include "Entities/Entity.h" diff --git a/src/SetChunkData.h b/src/SetChunkData.h index 2f3c3d6a3..6d3e76a82 100644 --- a/src/SetChunkData.h +++ b/src/SetChunkData.h @@ -116,10 +116,6 @@ protected: bool m_ShouldMarkDirty; }; -typedef SharedPtr<cSetChunkData> cSetChunkDataPtr; // TODO: Change to unique_ptr once we go C++11 -typedef std::vector<cSetChunkDataPtr> cSetChunkDataPtrs; - - diff --git a/src/SpawnPrepare.cpp b/src/SpawnPrepare.cpp index 3ea383ee9..4f9086772 100644 --- a/src/SpawnPrepare.cpp +++ b/src/SpawnPrepare.cpp @@ -12,17 +12,17 @@ class cSpawnPrepareCallback : public cChunkCoordCallback { public: - cSpawnPrepareCallback(cSpawnPrepare & a_SpawnPrepare) : - m_SpawnPrepare(a_SpawnPrepare) + cSpawnPrepareCallback(std::shared_ptr<cSpawnPrepare> a_SpawnPrepare) : + m_SpawnPrepare(std::move(a_SpawnPrepare)) { } protected: - cSpawnPrepare & m_SpawnPrepare; + std::shared_ptr<cSpawnPrepare> m_SpawnPrepare; virtual void Call(int a_ChunkX, int a_ChunkZ, bool a_IsSuccess) override { - m_SpawnPrepare.PreparedChunkCallback(a_ChunkX, a_ChunkZ); + m_SpawnPrepare->PreparedChunkCallback(a_ChunkX, a_ChunkZ); } }; @@ -30,16 +30,13 @@ protected: -cSpawnPrepare::cSpawnPrepare(cWorld & a_World, int a_SpawnChunkX, int a_SpawnChunkZ, int a_PrepareDistance, int a_FirstIdx): +cSpawnPrepare::cSpawnPrepare(cWorld & a_World, int a_PrepareDistance, std::function<void()> a_PreparationCompletedCallback): m_World(a_World), - m_SpawnChunkX(a_SpawnChunkX), - m_SpawnChunkZ(a_SpawnChunkZ), - m_PrepareDistance(a_PrepareDistance), - m_NextIdx(a_FirstIdx), - m_MaxIdx(a_PrepareDistance * a_PrepareDistance), + m_TotalChunks(a_PrepareDistance * a_PrepareDistance), m_NumPrepared(0), m_LastReportTime(std::chrono::steady_clock::now()), - m_LastReportChunkCount(0) + m_LastReportChunkCount(0), + m_PreparationCompletedCallback(std::move(a_PreparationCompletedCallback)) { } @@ -49,24 +46,50 @@ cSpawnPrepare::cSpawnPrepare(cWorld & a_World, int a_SpawnChunkX, int a_SpawnChu -void cSpawnPrepare::PrepareChunks(cWorld & a_World, int a_SpawnChunkX, int a_SpawnChunkZ, int a_PrepareDistance) +void cSpawnPrepare::PrepareChunks(cWorld & a_World, int a_PrepareDistance, std::function<void()> a_PreparationCompletedCallback) { + auto PerformanceAnalysisObject = std::make_shared<cSpawnPrepare>(a_World, a_PrepareDistance, a_PreparationCompletedCallback); + auto HalfPrepareDistance = (a_PrepareDistance - 1) / 2.f; + auto NegativePrepareDistance = FloorC(-HalfPrepareDistance), PositivePrepareDistance = FloorC(HalfPrepareDistance); - // Queue the initial chunks: - int MaxIdx = a_PrepareDistance * a_PrepareDistance; - int maxQueue = std::min(MaxIdx - 1, 100); // Number of chunks to queue at once - cSpawnPrepare prep(a_World, a_SpawnChunkX, a_SpawnChunkZ, a_PrepareDistance, maxQueue); - for (int i = 0; i < maxQueue; i++) + for (int ChunkX = NegativePrepareDistance; ChunkX <= PositivePrepareDistance; ++ChunkX) { - int chunkX, chunkZ; - prep.DecodeChunkCoords(i, chunkX, chunkZ); - a_World.PrepareChunk(chunkX, chunkZ, cpp14::make_unique<cSpawnPrepareCallback>(prep)); - } // for i + for (int ChunkZ = NegativePrepareDistance; ChunkZ <= PositivePrepareDistance; ++ChunkZ) + { + a_World.PrepareChunk(ChunkX, ChunkZ, cpp14::make_unique<cSpawnPrepareCallback>(PerformanceAnalysisObject)); + } + } +} + + + + + +void cSpawnPrepare::PreparedChunkCallback(int a_ChunkX, int a_ChunkZ) +{ + m_NumPrepared += 1; + if (m_NumPrepared == m_TotalChunks) + { + if (m_PreparationCompletedCallback) + { + m_PreparationCompletedCallback(); + } + + LOG("Preparing spawn (%s): completed!", m_World.GetName().c_str()); + return; + } - // Wait for the lighting thread to prepare everything. Event is set in the Call() callback: - if (MaxIdx > 0) + // Report progress every 1 second: + auto Now = std::chrono::steady_clock::now(); + if (Now - m_LastReportTime > std::chrono::seconds(1)) { - prep.m_EvtFinished.Wait(); + float PercentDone = static_cast<float>(m_NumPrepared * 100) / m_TotalChunks; + float ChunkSpeed = static_cast<float>((m_NumPrepared - m_LastReportChunkCount) * 1000) / std::chrono::duration_cast<std::chrono::milliseconds>(Now - m_LastReportTime).count(); + LOG("Preparing spawn (%s): %.02f%% (%d/%d; %.02f chunks / sec)", + m_World.GetName().c_str(), PercentDone, m_NumPrepared, m_TotalChunks, ChunkSpeed + ); + m_LastReportTime = Now; + m_LastReportChunkCount = m_NumPrepared; } } @@ -74,55 +97,207 @@ void cSpawnPrepare::PrepareChunks(cWorld & a_World, int a_SpawnChunkX, int a_Spa -void cSpawnPrepare::DecodeChunkCoords(int a_Idx, int & a_ChunkX, int & a_ChunkZ) +bool cSpawnPrepare::IsValidSpawnBiome(cWorld & a_World, int a_ChunkX, int a_ChunkZ) { - // A zigzag pattern from the top to bottom, each row alternating between forward-x and backward-x: - int z = a_Idx / m_PrepareDistance; - int x = a_Idx % m_PrepareDistance; - if ((z & 1) == 0) + auto Biome = a_World.GetBiomeAt(a_ChunkX, a_ChunkZ); + if ((Biome != EMCSBiome::biOcean) && (Biome != EMCSBiome::biFrozenOcean) && (Biome != EMCSBiome::biDeepOcean)) { - // Reverse every second row: - x = m_PrepareDistance - 1 - x; + return true; } - a_ChunkZ = m_SpawnChunkZ + z - m_PrepareDistance / 2; - a_ChunkX = m_SpawnChunkX + x - m_PrepareDistance / 2; + + return false; } -void cSpawnPrepare::PreparedChunkCallback(int a_ChunkX, int a_ChunkZ) +Vector3d cSpawnPrepare::GenerateRandomSpawn(cWorld & a_World, int a_PrepareDistance) { - // Check if this was the last chunk: - m_NumPrepared += 1; - if (m_NumPrepared >= m_MaxIdx) + LOGD("Generating random spawnpoint..."); + int ChunkX = 0, ChunkZ = 0; + + for (int CurrentRadius = 0; CurrentRadius <= a_PrepareDistance; ++CurrentRadius) { - m_EvtFinished.Set(); - // Must return here, because "this" may have gotten deleted by the previous line - return; + // Iterate through right and left sides + for (int PerpendicularRadius = -CurrentRadius; PerpendicularRadius <= CurrentRadius; ++PerpendicularRadius) + { + if (cSpawnPrepare::IsValidSpawnBiome(a_World, CurrentRadius, PerpendicularRadius)) + { + ChunkX = CurrentRadius; + ChunkZ = PerpendicularRadius; + goto IUsedAGotoEatThat; + } + + if (cSpawnPrepare::IsValidSpawnBiome(a_World, -CurrentRadius, PerpendicularRadius)) + { + ChunkX = -CurrentRadius; + ChunkZ = PerpendicularRadius; + goto IUsedAGotoEatThat; + } + } + + // Iterate through top and bottom sides, omitting the corners + for (int PerpendicularRadius = -CurrentRadius + 1; PerpendicularRadius < CurrentRadius; ++PerpendicularRadius) + { + if (cSpawnPrepare::IsValidSpawnBiome(a_World, PerpendicularRadius, CurrentRadius)) + { + ChunkX = PerpendicularRadius; + ChunkZ = CurrentRadius; + goto IUsedAGotoEatThat; + } + + if (cSpawnPrepare::IsValidSpawnBiome(a_World, PerpendicularRadius, -CurrentRadius)) + { + ChunkX = PerpendicularRadius; + ChunkZ = -CurrentRadius; + goto IUsedAGotoEatThat; + } + } } - // Queue another chunk, if appropriate: - if (m_NextIdx < m_MaxIdx) +IUsedAGotoEatThat: + + for (auto BlockX = ChunkX * cChunkDef::Width; BlockX != (ChunkX + 1) * cChunkDef::Width; ++BlockX) { - int chunkX, chunkZ; - DecodeChunkCoords(m_NextIdx, chunkX, chunkZ); - m_World.GetLightingThread().QueueChunk(chunkX, chunkZ, cpp14::make_unique<cSpawnPrepareCallback>(*this)); - m_NextIdx += 1; + for (auto BlockZ = ChunkZ * cChunkDef::Width; BlockZ != (ChunkZ + 1) * cChunkDef::Width; ++BlockZ) + { + Vector3d Position; + Position.x = static_cast<double>(BlockX); + Position.z = static_cast<double>(BlockZ); + + if (CanSpawnAt(a_World, Position.x, Position.y, Position.z)) + { + Position += {0.5, 0.0, 0.5}; + + LOGINFO("Generated spawn position at {%.2f, %.2f, %.2f}", Position.x, Position.y, Position.z); + return Position; + } + } } - // Report progress every 1 second: - auto Now = std::chrono::steady_clock::now(); - if (Now - m_LastReportTime > std::chrono::seconds(1)) + int Height; + VERIFY(a_World.TryGetHeight(0, 0, Height)); + + LOGWARNING("Did not find an acceptable spawnpoint. Defaulted to spawn at the origin, elevation %i blocks", Height); + return { 0.0, static_cast<double>(Height), 0.0 }; +} + + + + + +bool cSpawnPrepare::CanSpawnAt(cWorld & a_World, double a_X, double & a_Y, double a_Z) +{ + // All this blocks can only be found above ground. + // Apart from netherrack (as the Nether is technically a massive cave) + static const BLOCKTYPE ValidSpawnBlocks[] = { - float PercentDone = static_cast<float>(m_NumPrepared * 100) / m_MaxIdx; - float ChunkSpeed = static_cast<float>((m_NumPrepared - m_LastReportChunkCount) * 1000) / std::chrono::duration_cast<std::chrono::milliseconds>(Now - m_LastReportTime).count(); - LOG("Preparing spawn (%s): %.02f%% (%d/%d; %.02f chunks / sec)", - m_World.GetName().c_str(), PercentDone, m_NumPrepared.load(std::memory_order_seq_cst), m_MaxIdx, ChunkSpeed - ); - m_LastReportTime = Now; - m_LastReportChunkCount = m_NumPrepared; + E_BLOCK_GRASS, + E_BLOCK_SAND, + E_BLOCK_SNOW, + E_BLOCK_SNOW_BLOCK, + E_BLOCK_NETHERRACK, + E_BLOCK_END_STONE + }; + + static const int ValidSpawnBlocksCount = ARRAYCOUNT(ValidSpawnBlocks); + + // Increase this by two, because we need two more blocks for body and head + int HighestSpawnPoint; + VERIFY(a_World.TryGetHeight(static_cast<int>(a_X), static_cast<int>(a_Z), HighestSpawnPoint)); + HighestSpawnPoint += 2; + + const int LowestSpawnPoint = static_cast<int>(HighestSpawnPoint / 2.0f); + + for (int PotentialY = HighestSpawnPoint; PotentialY > LowestSpawnPoint; --PotentialY) + { + BLOCKTYPE HeadBlock = a_World.GetBlock(static_cast<int>(a_X), PotentialY, static_cast<int>(a_Z)); + + // Is this block safe for spawning + if (HeadBlock != E_BLOCK_AIR) + { + continue; + } + + BLOCKTYPE BodyBlock = a_World.GetBlock(static_cast<int>(a_X), PotentialY - 1, static_cast<int>(a_Z)); + + // Is this block safe for spawning + if (BodyBlock != E_BLOCK_AIR) + { + continue; + } + + BLOCKTYPE FloorBlock = a_World.GetBlock(static_cast<int>(a_X), PotentialY - 2, static_cast<int>(a_Z)); + + // Early out - Is the floor block air + if (FloorBlock == E_BLOCK_AIR) + { + continue; + } + + // Is the floor block ok + bool ValidSpawnBlock = false; + for (int BlockIndex = 0; BlockIndex < ValidSpawnBlocksCount; ++BlockIndex) + { + ValidSpawnBlock |= (ValidSpawnBlocks[BlockIndex] == FloorBlock); + } + + if (!ValidSpawnBlock) + { + continue; + } + + if (!CheckPlayerSpawnPoint(a_World, static_cast<int>(a_X), PotentialY - 1, static_cast<int>(a_Z))) + { + continue; + } + + a_Y = PotentialY - 1.0; + return true; } + + return false; } + + + + +bool cSpawnPrepare::CheckPlayerSpawnPoint(cWorld & a_World, int a_PosX, int a_PosY, int a_PosZ) +{ + // Check height bounds + if (!cChunkDef::IsValidHeight(a_PosY)) + { + return false; + } + + // Check that surrounding blocks are neither solid or liquid + static const Vector3i SurroundingCoords[] = + { + Vector3i(0, 0, 1), + Vector3i(1, 0, 1), + Vector3i(1, 0, 0), + Vector3i(1, 0, -1), + Vector3i(0, 0, -1), + Vector3i(-1, 0, -1), + Vector3i(-1, 0, 0), + Vector3i(-1, 0, 1), + }; + + static const int SurroundingCoordsCount = ARRAYCOUNT(SurroundingCoords); + + for (int CoordIndex = 0; CoordIndex < SurroundingCoordsCount; ++CoordIndex) + { + const int XPos = a_PosX + SurroundingCoords[CoordIndex].x; + const int ZPos = a_PosZ + SurroundingCoords[CoordIndex].z; + + const BLOCKTYPE BlockType = a_World.GetBlock(XPos, a_PosY, ZPos); + if (cBlockInfo::IsSolid(BlockType) || IsBlockLiquid(BlockType)) + { + return false; + } + } + + return true; +} diff --git a/src/SpawnPrepare.h b/src/SpawnPrepare.h index 1c7a42b2f..4d6064af4 100644 --- a/src/SpawnPrepare.h +++ b/src/SpawnPrepare.h @@ -12,25 +12,19 @@ class cSpawnPrepare { public: - static void PrepareChunks(cWorld & a_World, int a_SpawnChunkX, int a_SpawnChunkZ, int a_PrepareDistance); + cSpawnPrepare(cWorld & a_World, int a_PrepareDistance, std::function<void()> a_PreparationCompletedCallback); -protected: - cWorld & m_World; - int m_SpawnChunkX; - int m_SpawnChunkZ; - int m_PrepareDistance; + static void PrepareChunks(cWorld & a_World, int a_PrepareDistance, std::function<void()> a_PreparationCompletedCallback = {}); - /** The index of the next chunk to be queued in the lighting thread. */ - int m_NextIdx; + /** Generates a random spawnpoint on solid land by walking chunks and finding their biomes */ + static Vector3d GenerateRandomSpawn(cWorld & a_World, int a_PrepareDistance); - /** The maximum index of the prepared chunks. Queueing stops when m_NextIdx reaches this number. */ - int m_MaxIdx; +protected: + cWorld & m_World; + const int m_TotalChunks; /** Total number of chunks already finished preparing. Preparation finishes when this number reaches m_MaxIdx. */ - std::atomic<int> m_NumPrepared; - - /** Event used to signal that the preparation is finished. */ - cEvent m_EvtFinished; + int m_NumPrepared; /** The timestamp of the last progress report emitted. */ std::chrono::steady_clock::time_point m_LastReportTime; @@ -38,12 +32,19 @@ protected: /** Number of chunks prepared when the last progress report was emitted. */ int m_LastReportChunkCount; - cSpawnPrepare(cWorld & a_World, int a_SpawnChunkX, int a_SpawnChunkZ, int a_PrepareDistance, int a_FirstIdx); + std::function<void()> m_PreparationCompletedCallback; void PreparedChunkCallback(int a_ChunkX, int a_ChunkZ); - /** Decodes the index into chunk coords. Provides the specific chunk ordering. */ - void DecodeChunkCoords(int a_Idx, int & a_ChunkX, int & a_ChunkZ); + /** Returns if the biome of the given chunk coordinates is a valid spawn candidate. */ + static bool IsValidSpawnBiome(cWorld & a_World, int a_ChunkX, int a_ChunkZ); + + /** Can the specified coordinates be used as a spawn point? + Returns true if spawn position is valid and sets a_Y to the valid spawn height */ + static bool CanSpawnAt(cWorld & a_World, double a_X, double & a_Y, double a_Z); + + /** Check if player starting point is acceptable */ + static bool CheckPlayerSpawnPoint(cWorld & a_World, int a_PosX, int a_PosY, int a_PosZ); friend class cSpawnPrepareCallback; diff --git a/src/World.cpp b/src/World.cpp index c687e9549..399776320 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -62,6 +62,8 @@ #include "SpawnPrepare.h" #include "FastRandom.h" +#include <memory> + const int TIME_SUNSET = 12000; @@ -75,18 +77,6 @@ const int TIME_SPAWN_DIVISOR = 148; //////////////////////////////////////////////////////////////////////////////// -// cWorld::cLock: - -cWorld::cLock::cLock(cWorld & a_World) : - super(&(a_World.m_ChunkMap->GetCS())) -{ -} - - - - - -//////////////////////////////////////////////////////////////////////////////// // cWorld::cTickThread: cWorld::cTickThread::cTickThread(cWorld & a_World) : @@ -101,6 +91,10 @@ cWorld::cTickThread::cTickThread(cWorld & a_World) : void cWorld::cTickThread::Execute(void) { + m_World.Initialise(); + m_World.InitialiseSpawn(); + cRoot::Get()->GetPluginManager()->CallHookWorldStarted(m_World); + auto LastTime = std::chrono::steady_clock::now(); auto TickTime = std::chrono::duration_cast<std::chrono::milliseconds>(cTickTime(1)); @@ -108,9 +102,14 @@ void cWorld::cTickThread::Execute(void) { auto NowTime = std::chrono::steady_clock::now(); auto WaitTime = std::chrono::duration_cast<std::chrono::milliseconds>(NowTime - LastTime); - m_World.Tick(WaitTime, TickTime); - TickTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - NowTime); + if (m_World.m_ShouldTick) + { + m_World.Tick(WaitTime, TickTime); + } + m_World.TickQueuedTasks(); + + TickTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - NowTime); if (TickTime < cTickTime(1)) { // Stretch tick time until it's at least 1 tick @@ -119,6 +118,36 @@ void cWorld::cTickThread::Execute(void) LastTime = NowTime; } + + class DestructionCallback : public cEntityCallback + { + virtual bool Item(cEntity * a_Entity) override + { + a_Entity->Destroy(false); + return false; + } + } DestructionCallback; + m_World.ForEachEntity(DestructionCallback); + + // Write settings to file; these are all plugin changeable values - keep updated! + cIniFile IniFile; + IniFile.ReadFile(m_World.m_IniFileName); + if (m_World.GetDimension() == dimOverworld) + { + IniFile.SetValue("LinkedWorlds", "NetherWorldName", m_World.m_LinkedNetherWorldName); + IniFile.SetValue("LinkedWorlds", "EndWorldName", m_World.m_LinkedEndWorldName); + } + else + { + IniFile.SetValue("LinkedWorlds", "OverworldName", m_World.m_LinkedOverworldName); + } + IniFile.SetValueI("Physics", "TNTShrapnelLevel", static_cast<int>(m_World.m_TNTShrapnelLevel)); + IniFile.SetValueB("Mechanics", "CommandBlocksEnabled", m_World.m_bCommandBlocksEnabled); + IniFile.SetValueB("Mechanics", "UseChatPrefixes", m_World.m_bUseChatPrefixes); + IniFile.SetValueB("General", "IsDaylightCycleEnabled", m_World.m_IsDaylightCycleEnabled); + IniFile.SetValueI("General", "Weather", static_cast<int>(m_World.m_Weather)); + IniFile.SetValueI("General", "TimeInTicks", m_World.GetTimeOfDay()); + IniFile.WriteFile(m_World.m_IniFileName); } @@ -129,6 +158,7 @@ void cWorld::cTickThread::Execute(void) // cWorld: cWorld::cWorld(const AString & a_WorldName, eDimension a_Dimension, const AString & a_LinkedOverworldName) : + m_ShouldTick(true), m_WorldName(a_WorldName), m_LinkedOverworldName(a_LinkedOverworldName), m_IniFileName(m_WorldName + "/world.ini"), @@ -218,8 +248,6 @@ cWorld::~cWorld() delete m_LavaSimulator; m_LavaSimulator = nullptr; delete m_RedstoneSimulator; m_RedstoneSimulator = nullptr; - m_Storage.WaitForFinish(); - // Unload the scoreboard cScoreboardSerializer Serializer(m_WorldName, &m_Scoreboard); Serializer.Save(); @@ -351,29 +379,41 @@ bool cWorld::SetSpawn(double a_X, double a_Y, double a_Z) -void cWorld::InitializeSpawn(void) +void cWorld::InitialiseSpawn(void) { // For the debugging builds, don't make the server build too much world upon start: #if defined(_DEBUG) || defined(ANDROID_NDK) - const int DefaultViewDist = 9; + static const int DefaultViewDist = 9; #else - const int DefaultViewDist = 20; // Always prepare an area 20 chunks across, no matter what the actual cClientHandle::VIEWDISTANCE is + static const int DefaultViewDist = 20; // Always prepare an area 20 chunks across, no matter what the actual cClientHandle::VIEWDISTANCE is #endif // _DEBUG - if (!m_IsSpawnExplicitlySet) + int ViewDist; { - // Spawn position wasn't already explicitly set, enumerate random solid-land coordinate and then write it to the world configuration: - GenerateRandomSpawn(DefaultViewDist); - } + cIniFile IniFile; + IniFile.ReadFile(m_IniFileName); + ViewDist = IniFile.GetValueSetI("SpawnPosition", "PregenerateDistance", DefaultViewDist); + IniFile.WriteFile(m_IniFileName); - cIniFile IniFile; - IniFile.ReadFile(m_IniFileName); - int ViewDist = IniFile.GetValueSetI("SpawnPosition", "PregenerateDistance", DefaultViewDist); - IniFile.WriteFile(m_IniFileName); + } - int ChunkX = 0, ChunkZ = 0; - cChunkDef::BlockToChunk(FloorC(m_SpawnX), FloorC(m_SpawnZ), ChunkX, ChunkZ); - cSpawnPrepare::PrepareChunks(*this, ChunkX, ChunkZ, ViewDist); + if (m_IsSpawnExplicitlySet) + { + cSpawnPrepare::PrepareChunks(*this, ViewDist); + } + else + { + // Spawn position wasn't already explicitly set, enumerate random solid-land coordinate and then write it to the world configuration: + cSpawnPrepare::PrepareChunks( + *this, + ViewDist, + [this] + { + auto Spawn = cSpawnPrepare::GenerateRandomSpawn(*this, DefaultViewDist); + SetSpawn(Spawn.x, Spawn.y, Spawn.z); + } + ); + } #ifdef TEST_LINEBLOCKTRACER // DEBUG: Test out the cLineBlockTracer class by tracing a few lines: @@ -433,7 +473,7 @@ void cWorld::InitializeSpawn(void) -void cWorld::Start(void) +void cWorld::Initialise() { m_SpawnX = 0; m_SpawnY = cChunkDef::Height; @@ -617,7 +657,6 @@ void cWorld::Start(void) m_Storage.Start(this, m_StorageSchema, m_StorageCompressionFactor); m_Generator.Start(m_GeneratorCallbacks, m_GeneratorCallbacks, IniFile); m_ChunkSender.Start(); - m_TickThread.Start(); // Init of the spawn monster time (as they are supposed to have different spawn rate) m_LastSpawnMonster.insert(std::map<cMonster::eFamily, cTickTimeLong>::value_type(cMonster::mfHostile, cTickTimeLong(0))); @@ -638,203 +677,6 @@ void cWorld::Start(void) -void cWorld::GenerateRandomSpawn(int a_MaxSpawnRadius) -{ - LOGD("Generating random spawnpoint..."); - - // Number of checks to make sure we have a valid biome - // 100 checks will check across 400 chunks, we should have - // a valid biome by then. - static const int BiomeCheckCount = 100; - - // Make sure we are in a valid biome - Vector3i BiomeOffset = Vector3i(0, 0, 0); - for (int BiomeCheckIndex = 0; BiomeCheckIndex < BiomeCheckCount; ++BiomeCheckIndex) - { - EMCSBiome Biome = GetBiomeAt(BiomeOffset.x, BiomeOffset.z); - if ((Biome == EMCSBiome::biOcean) || (Biome == EMCSBiome::biFrozenOcean)) - { - BiomeOffset += Vector3d(cChunkDef::Width * 4, 0, 0); - continue; - } - - // Found a usable biome - // Spawn chunks so we can find a nice spawn. - int ChunkX = 0, ChunkZ = 0; - cChunkDef::BlockToChunk(BiomeOffset.x, BiomeOffset.z, ChunkX, ChunkZ); - cSpawnPrepare::PrepareChunks(*this, ChunkX, ChunkZ, a_MaxSpawnRadius); - break; - } - - // Check 0, 0 first. - double SpawnY = 0.0; - if (CanSpawnAt(BiomeOffset.x, SpawnY, BiomeOffset.z)) - { - SetSpawn(BiomeOffset.x + 0.5, SpawnY, BiomeOffset.z + 0.5); - - LOGINFO("Generated spawnpoint position at {%.2f, %.2f, %.2f}", m_SpawnX, m_SpawnY, m_SpawnZ); - return; - } - - // A search grid (searches clockwise around the origin) - static const int HalfChunk = static_cast<int>(cChunkDef::Width / 2.0f); - static const Vector3i ChunkOffset[] = - { - Vector3i(0, 0, HalfChunk), - Vector3i(HalfChunk, 0, HalfChunk), - Vector3i(HalfChunk, 0, 0), - Vector3i(HalfChunk, 0, -HalfChunk), - Vector3i(0, 0, -HalfChunk), - Vector3i(-HalfChunk, 0, -HalfChunk), - Vector3i(-HalfChunk, 0, 0), - Vector3i(-HalfChunk, 0, HalfChunk), - }; - - static const int PerRadiSearchCount = ARRAYCOUNT(ChunkOffset); - - for (int RadiusOffset = 1; RadiusOffset < (a_MaxSpawnRadius * 2); ++RadiusOffset) - { - for (int SearchGridIndex = 0; SearchGridIndex < PerRadiSearchCount; ++SearchGridIndex) - { - const Vector3i PotentialSpawn = BiomeOffset + (ChunkOffset[SearchGridIndex] * RadiusOffset); - - if (CanSpawnAt(PotentialSpawn.x, SpawnY, PotentialSpawn.z)) - { - SetSpawn(PotentialSpawn.x + 0.5, SpawnY, PotentialSpawn.z + 0.5); - - int ChunkX, ChunkZ; - cChunkDef::BlockToChunk(static_cast<int>(m_SpawnX), static_cast<int>(m_SpawnZ), ChunkX, ChunkZ); - cSpawnPrepare::PrepareChunks(*this, ChunkX, ChunkZ, a_MaxSpawnRadius); - - LOGINFO("Generated spawnpoint position at {%.2f, %.2f, %.2f}", m_SpawnX, m_SpawnY, m_SpawnZ); - return; - } - } - } - - m_SpawnY = GetHeight(static_cast<int>(m_SpawnX), static_cast<int>(m_SpawnZ)); - LOGWARNING("Did not find an acceptable spawnpoint. Generated a random spawnpoint position at {%.2f, %.2f, %.2f}", m_SpawnX, m_SpawnY, m_SpawnZ); -} - - - - - -bool cWorld::CanSpawnAt(double a_X, double & a_Y, double a_Z) -{ - // All this blocks can only be found above ground. - // Apart from netherrack (as the Nether is technically a massive cave) - static const BLOCKTYPE ValidSpawnBlocks[] = - { - E_BLOCK_GRASS, - E_BLOCK_SAND, - E_BLOCK_SNOW, - E_BLOCK_SNOW_BLOCK, - E_BLOCK_NETHERRACK - }; - - static const int ValidSpawnBlocksCount = ARRAYCOUNT(ValidSpawnBlocks); - - // Increase this by two, because we need two more blocks for body and head - static const int HighestSpawnPoint = GetHeight(static_cast<int>(a_X), static_cast<int>(a_Z)) + 2; - static const int LowestSpawnPoint = static_cast<int>(HighestSpawnPoint / 2.0f); - - for (int PotentialY = HighestSpawnPoint; PotentialY > LowestSpawnPoint; --PotentialY) - { - BLOCKTYPE HeadBlock = GetBlock(static_cast<int>(a_X), PotentialY, static_cast<int>(a_Z)); - - // Is this block safe for spawning - if (HeadBlock != E_BLOCK_AIR) - { - continue; - } - - BLOCKTYPE BodyBlock = GetBlock(static_cast<int>(a_X), PotentialY - 1, static_cast<int>(a_Z)); - - // Is this block safe for spawning - if (BodyBlock != E_BLOCK_AIR) - { - continue; - } - - BLOCKTYPE FloorBlock = GetBlock(static_cast<int>(a_X), PotentialY - 2, static_cast<int>(a_Z)); - - // Early out - Is the floor block air - if (FloorBlock == E_BLOCK_AIR) - { - continue; - } - - // Is the floor block ok - bool ValidSpawnBlock = false; - for (int BlockIndex = 0; BlockIndex < ValidSpawnBlocksCount; ++BlockIndex) - { - ValidSpawnBlock |= (ValidSpawnBlocks[BlockIndex] == FloorBlock); - } - - if (!ValidSpawnBlock) - { - continue; - } - - if (!CheckPlayerSpawnPoint(static_cast<int>(a_X), PotentialY - 1, static_cast<int>(a_Z))) - { - continue; - } - - a_Y = PotentialY - 1.0; - return true; - } - - return false; -} - - - - - -bool cWorld::CheckPlayerSpawnPoint(int a_PosX, int a_PosY, int a_PosZ) -{ - // Check height bounds - if (!cChunkDef::IsValidHeight(a_PosY)) - { - return false; - } - - // Check that surrounding blocks are neither solid or liquid - static const Vector3i SurroundingCoords[] = - { - Vector3i(0, 0, 1), - Vector3i(1, 0, 1), - Vector3i(1, 0, 0), - Vector3i(1, 0, -1), - Vector3i(0, 0, -1), - Vector3i(-1, 0, -1), - Vector3i(-1, 0, 0), - Vector3i(-1, 0, 1), - }; - - static const int SurroundingCoordsCount = ARRAYCOUNT(SurroundingCoords); - - for (int CoordIndex = 0; CoordIndex < SurroundingCoordsCount; ++CoordIndex) - { - const int XPos = a_PosX + SurroundingCoords[CoordIndex].x; - const int ZPos = a_PosZ + SurroundingCoords[CoordIndex].z; - - const BLOCKTYPE BlockType = GetBlock(XPos, a_PosY, ZPos); - if (cBlockInfo::IsSolid(BlockType) || IsBlockLiquid(BlockType)) - { - return false; - } - } - - return true; -} - - - - - eWeather cWorld::ChooseNewWeather() { // Pick a new weather. Only reasonable transitions allowed: @@ -948,41 +790,18 @@ void cWorld::InitialiseAndLoadMobSpawningValues(cIniFile & a_IniFile) void cWorld::Stop(void) { - // Delete the clients that have been in this world: - { - cCSLock Lock(m_CSClients); - for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) - { - (*itr)->Destroy(); - } // for itr - m_Clients[] - m_Clients.clear(); - } - - // Write settings to file; these are all plugin changeable values - keep updated! - cIniFile IniFile; - IniFile.ReadFile(m_IniFileName); - if (GetDimension() == dimOverworld) - { - IniFile.SetValue("LinkedWorlds", "NetherWorldName", m_LinkedNetherWorldName); - IniFile.SetValue("LinkedWorlds", "EndWorldName", m_LinkedEndWorldName); - } - else - { - IniFile.SetValue("LinkedWorlds", "OverworldName", m_LinkedOverworldName); - } - IniFile.SetValueI("Physics", "TNTShrapnelLevel", static_cast<int>(m_TNTShrapnelLevel)); - IniFile.SetValueB("Mechanics", "CommandBlocksEnabled", m_bCommandBlocksEnabled); - IniFile.SetValueB("Mechanics", "UseChatPrefixes", m_bUseChatPrefixes); - IniFile.SetValueB("General", "IsDaylightCycleEnabled", m_IsDaylightCycleEnabled); - IniFile.SetValueI("General", "Weather", static_cast<int>(m_Weather)); - IniFile.SetValueI("General", "TimeInTicks", GetTimeOfDay()); - IniFile.WriteFile(m_IniFileName); + // Stop calling cWorld::Tick; prevents occurences where a worker thread is stopped but + // SUDDENLY, A WILD CHUNK SAVE TASK APPEARS!!! + // NB: queued tasks which may be generated by worker threads are still processed + m_ShouldTick = false; - m_TickThread.Stop(); m_Lighting.Stop(); m_Generator.Stop(); m_ChunkSender.Stop(); m_Storage.Stop(); + + // Finally, stop the tick thread + m_TickThread.Stop(); } @@ -994,17 +813,6 @@ void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_La // Call the plugins cPluginManager::Get()->CallHookWorldTick(*this, a_Dt, a_LastTickDurationMSec); - // Set any chunk data that has been queued for setting: - cSetChunkDataPtrs SetChunkDataQueue; - { - cCSLock Lock(m_CSSetChunkDataQueue); - std::swap(SetChunkDataQueue, m_SetChunkDataQueue); - } - for (cSetChunkDataPtrs::iterator itr = SetChunkDataQueue.begin(), end = SetChunkDataQueue.end(); itr != end; ++itr) - { - SetChunkData(**itr); - } // for itr - SetChunkDataQueue[] - m_WorldAge += a_Dt; if (m_IsDaylightCycleEnabled) @@ -1029,29 +837,11 @@ void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_La } } - // Add entities waiting in the queue to be added: - { - cCSLock Lock(m_CSEntitiesToAdd); - for (auto & Entity : m_EntitiesToAdd) - { - Entity->SetWorld(this); - m_ChunkMap->AddEntity(Entity); - ASSERT(!Entity->IsTicking()); - Entity->SetIsTicking(true); - } - m_EntitiesToAdd.clear(); - } - - // Add players waiting in the queue to be added: - AddQueuedPlayers(); - m_ChunkMap->Tick(a_Dt); TickMobs(a_Dt); m_MapManager.TickMaps(); - TickClients(static_cast<float>(a_Dt.count())); TickQueuedBlocks(); - TickQueuedTasks(); GetSimulatorManager()->Simulate(static_cast<float>(a_Dt.count())); @@ -1066,7 +856,6 @@ void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_La { UnloadUnusedChunks(); } - } @@ -1109,9 +898,6 @@ void cWorld::TickWeather(float a_Dt) void cWorld::TickMobs(std::chrono::milliseconds a_Dt) { - // _X 2013_10_22: This is a quick fix for #283 - the world needs to be locked while ticking mobs - cWorld::cLock Lock(*this); - // before every Mob action, we have to count them depending on the distance to players, on their family ... cMobCensus MobCensus; m_ChunkMap->CollectMobCensus(MobCensus); @@ -1213,7 +999,10 @@ void cWorld::TickQueuedTasks(void) } // Partition everything to be executed by returning false to move to end of list if time reached - auto MoveBeginIterator = std::partition(m_Tasks.begin(), m_Tasks.end(), [this](const decltype(m_Tasks)::value_type & a_Task) + auto MoveBeginIterator = std::partition( + m_Tasks.begin(), + m_Tasks.end(), + [this](const auto & a_Task) { if (a_Task.first < std::chrono::duration_cast<cTickTimeLong>(m_WorldAge).count()) { @@ -1233,60 +1022,10 @@ void cWorld::TickQueuedTasks(void) } // Execute each task: - for (const auto & Task : Tasks) + for (auto & Task : Tasks) { Task.second(*this); - } // for itr - m_Tasks[] -} - - - - -void cWorld::TickClients(float a_Dt) -{ - cClientHandlePtrs RemoveClients; - { - cCSLock Lock(m_CSClients); - - // Remove clients scheduled for removal: - for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr) - { - for (auto itrC = m_Clients.begin(), endC = m_Clients.end(); itrC != endC; ++itrC) - { - if (itrC->get() == *itr) - { - m_Clients.erase(itrC); - break; - } - } - } // for itr - m_ClientsToRemove[] - m_ClientsToRemove.clear(); - - // Add clients scheduled for adding: - for (auto itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr) - { - ASSERT(std::find(m_Clients.begin(), m_Clients.end(), *itr) == m_Clients.end()); - m_Clients.push_back(*itr); - } // for itr - m_ClientsToRemove[] - m_ClientsToAdd.clear(); - - // Tick the clients, take out those that have been destroyed into RemoveClients - for (auto itr = m_Clients.begin(); itr != m_Clients.end();) - { - if ((*itr)->IsDestroyed()) - { - // Remove the client later, when CS is not held, to avoid deadlock - RemoveClients.push_back(*itr); - itr = m_Clients.erase(itr); - continue; - } - (*itr)->Tick(a_Dt); - ++itr; - } // for itr - m_Clients[] } - - // Delete the clients queued for removal: - RemoveClients.clear(); } @@ -1412,29 +1151,26 @@ void cWorld::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_Blo m_ChunkMap->DoExplosionAt(a_ExplosionSize, a_BlockX, a_BlockY, a_BlockZ, BlocksAffected); BroadcastSoundEffect("random.explode", static_cast<double>(a_BlockX), static_cast<double>(a_BlockY), static_cast<double>(a_BlockZ), 1.0f, 0.6f); + for (const auto & Player : m_Players) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + cClientHandle * ch = Player->GetClientHandle(); + if (ch == nullptr) { - cClientHandle * ch = (*itr)->GetClientHandle(); - if (ch == nullptr) - { - continue; - } + continue; + } - Vector3d distance_explosion = (*itr)->GetPosition() - explosion_pos; - if (distance_explosion.SqrLength() < 4096.0) + Vector3d distance_explosion = Player->GetPosition() - explosion_pos; + if (distance_explosion.SqrLength() < 4096.0) + { + double real_distance = std::max(0.004, distance_explosion.Length()); + double power = a_ExplosionSize / real_distance; + if (power <= 1) { - double real_distance = std::max(0.004, distance_explosion.Length()); - double power = a_ExplosionSize / real_distance; - if (power <= 1) - { - power = 0; - } - distance_explosion.Normalize(); - distance_explosion *= power; - ch->SendExplosion(a_BlockX, a_BlockY, a_BlockZ, static_cast<float>(a_ExplosionSize), BlocksAffected, distance_explosion); + power = 0; } + distance_explosion.Normalize(); + distance_explosion *= power; + ch->SendExplosion(a_BlockX, a_BlockY, a_BlockZ, static_cast<float>(a_ExplosionSize), BlocksAffected, distance_explosion); } } @@ -2390,10 +2126,9 @@ void cWorld::BroadcastBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cons void cWorld::BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude, eMessageType a_ChatPrefix) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed()) { continue; @@ -2408,10 +2143,9 @@ void cWorld::BroadcastChat(const AString & a_Message, const cClientHandle * a_Ex void cWorld::BroadcastChat(const cCompositeChat & a_Message, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed()) { continue; @@ -2543,10 +2277,9 @@ void cWorld::BroadcastEntityAnimation(const cEntity & a_Entity, char a_Animation void cWorld::BroadcastPlayerListAddPlayer(const cPlayer & a_Player, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed()) { continue; @@ -2561,10 +2294,9 @@ void cWorld::BroadcastPlayerListAddPlayer(const cPlayer & a_Player, const cClien void cWorld::BroadcastPlayerListRemovePlayer(const cPlayer & a_Player, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn()) { continue; @@ -2579,10 +2311,9 @@ void cWorld::BroadcastPlayerListRemovePlayer(const cPlayer & a_Player, const cCl void cWorld::BroadcastPlayerListUpdateGameMode(const cPlayer & a_Player, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed()) { continue; @@ -2597,10 +2328,9 @@ void cWorld::BroadcastPlayerListUpdateGameMode(const cPlayer & a_Player, const c void cWorld::BroadcastPlayerListUpdatePing(const cPlayer & a_Player, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed()) { continue; @@ -2615,10 +2345,9 @@ void cWorld::BroadcastPlayerListUpdatePing(const cPlayer & a_Player, const cClie void cWorld::BroadcastPlayerListUpdateDisplayName(const cPlayer & a_Player, const AString & a_CustomName, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed()) { continue; @@ -2642,10 +2371,9 @@ void cWorld::BroadcastRemoveEntityEffect(const cEntity & a_Entity, int a_EffectI void cWorld::BroadcastScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed()) { continue; @@ -2660,10 +2388,9 @@ void cWorld::BroadcastScoreboardObjective(const AString & a_Name, const AString void cWorld::BroadcastScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed()) { continue; @@ -2678,10 +2405,9 @@ void cWorld::BroadcastScoreUpdate(const AString & a_Objective, const AString & a void cWorld::BroadcastDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed()) { continue; @@ -2723,10 +2449,9 @@ void cWorld::BroadcastSpawnEntity(cEntity & a_Entity, const cClientHandle * a_Ex void cWorld::BroadcastTeleportEntity(const cEntity & a_Entity, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed()) { continue; @@ -2750,10 +2475,9 @@ void cWorld::BroadcastThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ, cons void cWorld::BroadcastTimeUpdate(const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed()) { continue; @@ -2777,10 +2501,9 @@ void cWorld::BroadcastUseBed(const cEntity & a_Entity, int a_BlockX, int a_Block void cWorld::BroadcastWeather(eWeather a_Weather, const cClientHandle * a_Exclude) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed()) { continue; @@ -2829,28 +2552,30 @@ void cWorld::MarkChunkSaved (int a_ChunkX, int a_ChunkZ) -void cWorld::QueueSetChunkData(const cSetChunkDataPtr & a_SetChunkData) +void cWorld::QueueSetChunkData(cSetChunkData & a_SetChunkData) { - ASSERT(IsChunkQueued(a_SetChunkData->GetChunkX(), a_SetChunkData->GetChunkZ())); - // Validate biomes, if needed: - if (!a_SetChunkData->AreBiomesValid()) + if (!a_SetChunkData.AreBiomesValid()) { // The biomes are not assigned, get them from the generator: - m_Generator.GenerateBiomes(a_SetChunkData->GetChunkX(), a_SetChunkData->GetChunkZ(), a_SetChunkData->GetBiomes()); - a_SetChunkData->MarkBiomesValid(); + m_Generator.GenerateBiomes(a_SetChunkData.GetChunkX(), a_SetChunkData.GetChunkZ(), a_SetChunkData.GetBiomes()); + a_SetChunkData.MarkBiomesValid(); } // Validate heightmap, if needed: - if (!a_SetChunkData->IsHeightMapValid()) + if (!a_SetChunkData.IsHeightMapValid()) { - a_SetChunkData->CalculateHeightMap(); + a_SetChunkData.CalculateHeightMap(); } - // Store a copy of the data in the queue: - // TODO: If the queue is too large, wait for it to get processed. Not likely, though. - cCSLock Lock(m_CSSetChunkDataQueue); - m_SetChunkDataQueue.push_back(a_SetChunkData); + cpp14::move_on_copy_wrapper<std::remove_reference<decltype(a_SetChunkData)>::type> SetChunkData(std::move(a_SetChunkData)); + QueueTask( + [SetChunkData](cWorld & a_World) + { + // TODO: make SetChunkData and all called functions const + a_World.SetChunkData(const_cast<cSetChunkData &>(SetChunkData.value)); + } + ); } @@ -2864,7 +2589,7 @@ void cWorld::SetChunkData(cSetChunkData & a_SetChunkData) m_ChunkMap->SetChunkData(a_SetChunkData); - // Initialize the entities (outside the m_ChunkMap's CS, to fix FS #347): + // Initialise the entities: cEntityList Entities; std::swap(a_SetChunkData.GetEntities(), Entities); for (cEntityList::iterator itr = Entities.begin(), end = Entities.end(); itr != end; ++itr) @@ -2872,31 +2597,10 @@ void cWorld::SetChunkData(cSetChunkData & a_SetChunkData) (*itr)->Initialize(*this); } - // If a client is requesting this chunk, send it to them: - int ChunkX = a_SetChunkData.GetChunkX(); - int ChunkZ = a_SetChunkData.GetChunkZ(); - cChunkSender & ChunkSender = m_ChunkSender; - DoWithChunk( - ChunkX, ChunkZ, - [&ChunkSender] (cChunk & a_Chunk) -> bool - { - if (a_Chunk.HasAnyClients()) - { - ChunkSender.QueueSendChunkTo( - a_Chunk.GetPosX(), - a_Chunk.GetPosZ(), - cChunkSender::E_CHUNK_PRIORITY_MEDIUM, - a_Chunk.GetAllClients() - ); - } - return true; - } - ); - // Save the chunk right after generating, so that we don't have to generate it again on next run if (a_SetChunkData.ShouldMarkDirty()) { - m_Storage.QueueSaveChunk(ChunkX, ChunkZ); + m_Storage.QueueSaveChunk(a_SetChunkData.GetChunkX(), a_SetChunkData.GetChunkZ()); } } @@ -2992,41 +2696,33 @@ void cWorld::CollectPickupsByPlayer(cPlayer & a_Player) void cWorld::AddPlayer(cPlayer * a_Player) { - cCSLock Lock(m_CSPlayersToAdd); - m_PlayersToAdd.push_back(a_Player); + ASSERT(m_TickThread.IsCurrentThread()); + + m_Players.emplace_back(a_Player); + + // Stream chunks to all eligible clients: + auto Client = a_Player->GetClientHandle(); + if (Client != nullptr) + { + Client->SendHealth(); + Client->SendWholeInventory(*a_Player->GetWindow()); + + if (GetDimension() == dimOverworld) + { + Client->SendWeather(GetWeather()); + } + } + // Don't worry, the client handle may have been deleted right after a world move operation was queued } -void cWorld::RemovePlayer(cPlayer * a_Player, bool a_RemoveFromChunk) +void cWorld::RemovePlayer(cPlayer * a_Player) { - if (a_RemoveFromChunk) - { - // 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 - m_ChunkMap->RemoveEntity(a_Player); - } - { - cCSLock Lock(m_CSPlayersToAdd); - m_PlayersToAdd.remove(a_Player); - } - { - cCSLock Lock(m_CSPlayers); - LOGD("Removing player %s from world \"%s\"", a_Player->GetName().c_str(), m_WorldName.c_str()); - m_Players.remove(a_Player); - } - - // Remove the player's client from the list of clients to be ticked: - cClientHandle * Client = a_Player->GetClientHandle(); - if (Client != nullptr) - { - Client->RemoveFromWorld(); - m_ChunkMap->RemoveClientFromChunks(Client); - cCSLock Lock(m_CSClients); - m_ClientsToRemove.push_back(Client); - } + LOGD("Removing player %s from world \"%s\"", a_Player->GetName().c_str(), m_WorldName.c_str()); + m_Players.erase(std::remove(m_Players.begin(), m_Players.end(), a_Player), m_Players.end()); } @@ -3036,8 +2732,7 @@ void cWorld::RemovePlayer(cPlayer * a_Player, bool a_RemoveFromChunk) bool cWorld::ForEachPlayer(cPlayerListCallback & a_Callback) { // Calls the callback for each player in the list - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(), itr2 = itr; itr != m_Players.end(); itr = itr2) + for (auto itr = m_Players.begin(), itr2 = itr; itr != m_Players.end(); itr = itr2) { ++itr2; if (!(*itr)->IsTicking()) @@ -3059,19 +2754,19 @@ bool cWorld::ForEachPlayer(cPlayerListCallback & a_Callback) bool cWorld::DoWithPlayer(const AString & a_PlayerName, cPlayerListCallback & a_Callback) { // Calls the callback for the specified player in the list - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - if (!(*itr)->IsTicking()) + if (!Player->IsTicking()) { continue; } - if (NoCaseCompare((*itr)->GetName(), a_PlayerName) == 0) + + if (NoCaseCompare(Player->GetName(), a_PlayerName) == 0) { - a_Callback.Item(*itr); + a_Callback.Item(Player); return true; } - } // for itr - m_Players[] + } return false; } @@ -3085,24 +2780,24 @@ bool cWorld::FindAndDoWithPlayer(const AString & a_PlayerNameHint, cPlayerListCa size_t BestRating = 0; size_t NameLength = a_PlayerNameHint.length(); - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - if (!(*itr)->IsTicking()) + if (!Player->IsTicking()) { continue; } - size_t Rating = RateCompareString (a_PlayerNameHint, (*itr)->GetName()); + + size_t Rating = RateCompareString (a_PlayerNameHint, Player->GetName()); if (Rating >= BestRating) { - BestMatch = *itr; + BestMatch = Player; BestRating = Rating; } if (Rating == NameLength) // Perfect match { break; } - } // for itr - m_Players[] + } if (BestMatch != nullptr) { @@ -3117,16 +2812,16 @@ bool cWorld::FindAndDoWithPlayer(const AString & a_PlayerNameHint, cPlayerListCa bool cWorld::DoWithPlayerByUUID(const AString & a_PlayerUUID, cPlayerListCallback & a_Callback) { - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - if (!(*itr)->IsTicking()) + if (!Player->IsTicking()) { continue; } - if ((*itr)->GetUUID() == a_PlayerUUID) + + if (Player->GetUUID() == a_PlayerUUID) { - return a_Callback.Item(*itr); + return a_Callback.Item(Player); } } return false; @@ -3144,14 +2839,14 @@ cPlayer * cWorld::FindClosestPlayer(const Vector3d & a_Pos, float a_SightLimit, double ClosestDistance = a_SightLimit; cPlayer * ClosestPlayer = nullptr; - cCSLock Lock(m_CSPlayers); - for (cPlayerList::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - if (!(*itr)->IsTicking()) + if (!Player->IsTicking()) { continue; } - Vector3f Pos = (*itr)->GetPosition(); + + Vector3f Pos = Player->GetPosition(); double Distance = (Pos - a_Pos).Length(); if (Distance < ClosestDistance) @@ -3161,13 +2856,13 @@ cPlayer * cWorld::FindClosestPlayer(const Vector3d & a_Pos, float a_SightLimit, if (!LineOfSight.Trace(a_Pos, (Pos - a_Pos), static_cast<int>((Pos - a_Pos).Length()))) { ClosestDistance = Distance; - ClosestPlayer = *itr; + ClosestPlayer = Player; } } else { ClosestDistance = Distance; - ClosestPlayer = *itr; + ClosestPlayer = Player; } } } @@ -3181,13 +2876,12 @@ cPlayer * cWorld::FindClosestPlayer(const Vector3d & a_Pos, float a_SightLimit, void cWorld::SendPlayerList(cPlayer * a_DestPlayer) { // Sends the playerlist to a_DestPlayer - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); + cClientHandle * ch = Player->GetClientHandle(); if ((ch != nullptr) && !ch->IsDestroyed()) { - a_DestPlayer->GetClientHandle()->SendPlayerListAddPlayer(*(*itr)); + a_DestPlayer->GetClientHandle()->SendPlayerListAddPlayer(*Player); } } } @@ -3225,19 +2919,6 @@ bool cWorld::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_ bool cWorld::DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback & a_Callback) { - // First check the entities-to-add: - { - cCSLock Lock(m_CSEntitiesToAdd); - for (auto & ent: m_EntitiesToAdd) - { - if (ent->GetUniqueID() == a_UniqueID) - { - a_Callback.Item(ent); - return true; - } - } // for ent - m_EntitiesToAdd[] - } - // Then check the chunkmap: return m_ChunkMap->DoWithEntityByID(a_UniqueID, a_Callback); } @@ -3255,7 +2936,7 @@ void cWorld::CompareChunkClients(int a_ChunkX1, int a_ChunkZ1, int a_ChunkX2, in -bool cWorld::AddChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client) +bool cWorld::AddChunkClient(int a_ChunkX, int a_ChunkZ, const std::shared_ptr<cClientHandle> & a_Client) { return m_ChunkMap->AddChunkClient(a_ChunkX, a_ChunkZ, a_Client); } @@ -3264,7 +2945,7 @@ bool cWorld::AddChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client -void cWorld::RemoveChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client) +void cWorld::RemoveChunkClient(int a_ChunkX, int a_ChunkZ, const std::shared_ptr<cClientHandle> & a_Client) { m_ChunkMap->RemoveChunkClient(a_ChunkX, a_ChunkZ, a_Client); } @@ -3273,43 +2954,6 @@ void cWorld::RemoveChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Cli -void cWorld::RemoveClientFromChunks(cClientHandle * a_Client) -{ - m_ChunkMap->RemoveClientFromChunks(a_Client); -} - - - - - -void cWorld::SendChunkTo(int a_ChunkX, int a_ChunkZ, cChunkSender::eChunkPriority a_Priority, cClientHandle * a_Client) -{ - m_ChunkSender.QueueSendChunkTo(a_ChunkX, a_ChunkZ, a_Priority, a_Client); -} - - - - - -void cWorld::ForceSendChunkTo(int a_ChunkX, int a_ChunkZ, cChunkSender::eChunkPriority a_Priority, cClientHandle * a_Client) -{ - a_Client->AddWantedChunk(a_ChunkX, a_ChunkZ); - m_ChunkSender.QueueSendChunkTo(a_ChunkX, a_ChunkZ, a_Priority, a_Client); -} - - - - - -void cWorld::RemoveClientFromChunkSender(cClientHandle * a_Client) -{ - m_ChunkSender.RemoveClient(a_Client); -} - - - - - void cWorld::TouchChunk(int a_ChunkX, int a_ChunkZ) { m_ChunkMap->TouchChunk(a_ChunkX, a_ChunkZ); @@ -3500,16 +3144,6 @@ void cWorld::QueueSaveAllChunks(void) -void cWorld::QueueTask(std::function<void(cWorld &)> a_Task) -{ - cCSLock Lock(m_CSTasks); - m_Tasks.emplace_back(0, a_Task); -} - - - - - void cWorld::ScheduleTask(int a_DelayTicks, std::function<void (cWorld &)> a_Task) { Int64 TargetTick = a_DelayTicks + std::chrono::duration_cast<cTickTimeLong>(m_WorldAge).count(); @@ -3517,7 +3151,7 @@ void cWorld::ScheduleTask(int a_DelayTicks, std::function<void (cWorld &)> a_Tas // Insert the task into the list of scheduled tasks { cCSLock Lock(m_CSTasks); - m_Tasks.emplace_back(TargetTick, a_Task); + m_Tasks.emplace_back(TargetTick, decltype(m_Tasks)::value_type::second_type(a_Task)); } } @@ -3527,9 +3161,6 @@ void cWorld::ScheduleTask(int a_DelayTicks, std::function<void (cWorld &)> a_Tas void cWorld::AddEntity(cEntity * a_Entity) { - a_Entity->SetWorld(this); - cCSLock Lock(m_CSEntitiesToAdd); - m_EntitiesToAdd.push_back(a_Entity); } @@ -3538,20 +3169,8 @@ void cWorld::AddEntity(cEntity * a_Entity) bool cWorld::HasEntity(UInt32 a_UniqueID) { - // Check if the entity is in the queue to be added to the world: - { - cCSLock Lock(m_CSEntitiesToAdd); - for (cEntityList::const_iterator itr = m_EntitiesToAdd.begin(), end = m_EntitiesToAdd.end(); itr != end; ++itr) - { - if ((*itr)->GetUniqueID() == a_UniqueID) - { - return true; - } - } // for itr - m_EntitiesToAdd[] - } - // Check if the entity is in the chunkmap: - if (m_ChunkMap.get() == nullptr) + if (m_ChunkMap == nullptr) { // Chunkmap has already been destroyed, there are no entities anymore. return false; @@ -3740,13 +3359,12 @@ void cWorld::TabCompleteUserName(const AString & a_Text, AStringVector & a_Resul std::vector<pair_t> UsernamesByWeight; - cCSLock Lock(m_CSPlayers); - for (cPlayerList::iterator itr = m_Players.begin(), end = m_Players.end(); itr != end; ++itr) + for (const auto & Player : m_Players) { - AString PlayerName ((*itr)->GetName()); - if ((*itr)->HasCustomName()) + AString PlayerName = Player->GetName(); + if (Player->HasCustomName()) { - PlayerName = (*itr)->GetCustomName(); + PlayerName = Player->GetCustomName(); } AString::size_type Found = StrToLower(PlayerName).find(StrToLower(LastWord)); // Try to find last word in playername @@ -3757,7 +3375,6 @@ void cWorld::TabCompleteUserName(const AString & a_Text, AStringVector & a_Resul UsernamesByWeight.push_back(std::make_pair(Found, PlayerName)); // Match! Store it with the position of the match as a weight } - Lock.Unlock(); std::sort(UsernamesByWeight.begin(), UsernamesByWeight.end()); // Sort lexicographically (by the first value, then second), so higher weights (usernames with match closer to start) come first (#1274) @@ -3889,66 +3506,6 @@ cFluidSimulator * cWorld::InitializeFluidSimulator(cIniFile & a_IniFile, const c - -void cWorld::AddQueuedPlayers(void) -{ - ASSERT(m_TickThread.IsCurrentThread()); - - // Grab the list of players to add, it has to be locked to access it: - cPlayerList PlayersToAdd; - { - cCSLock Lock(m_CSPlayersToAdd); - std::swap(PlayersToAdd, m_PlayersToAdd); - } - - // Add all the players in the grabbed list: - { - cCSLock Lock(m_CSPlayers); - for (auto Player : PlayersToAdd) - { - ASSERT(std::find(m_Players.begin(), m_Players.end(), Player) == m_Players.end()); // Is it already in the list? HOW? - LOGD("Adding player %s to world \"%s\".", Player->GetName().c_str(), m_WorldName.c_str()); - - m_Players.push_back(Player); - Player->SetWorld(this); - - // Add to chunkmap, if not already there (Spawn vs MoveToWorld): - m_ChunkMap->AddEntityIfNotPresent(Player); - ASSERT(!Player->IsTicking()); - Player->SetIsTicking(true); - } // for itr - PlayersToAdd[] - } // Lock(m_CSPlayers) - - // Add all the players' clienthandles: - { - cCSLock Lock(m_CSClients); - for (cPlayerList::iterator itr = PlayersToAdd.begin(), end = PlayersToAdd.end(); itr != end; ++itr) - { - cClientHandlePtr Client = (*itr)->GetClientHandlePtr(); - if (Client != nullptr) - { - m_Clients.push_back(Client); - } - } // for itr - PlayersToAdd[] - } // Lock(m_CSClients) - - // Stream chunks to all eligible clients: - for (cPlayerList::iterator itr = PlayersToAdd.begin(), end = PlayersToAdd.end(); itr != end; ++itr) - { - cClientHandle * Client = (*itr)->GetClientHandle(); - if (Client != nullptr) - { - Client->SendPlayerMoveLook(); - Client->SendHealth(); - Client->SendWholeInventory(*(*itr)->GetWindow()); - } - } // for itr - PlayersToAdd[] -} - - - - - //////////////////////////////////////////////////////////////////////////////// // cWorld::cChunkGeneratorCallbacks: @@ -3966,7 +3523,7 @@ void cWorld::cChunkGeneratorCallbacks::OnChunkGenerated(cChunkDesc & a_ChunkDesc cChunkDef::BlockNibbles BlockMetas; a_ChunkDesc.CompressBlockMetas(BlockMetas); - cSetChunkDataPtr SetChunkData(new cSetChunkData( + auto SetChunkData(cSetChunkData( a_ChunkDesc.GetChunkX(), a_ChunkDesc.GetChunkZ(), a_ChunkDesc.GetBlockTypes(), BlockMetas, nullptr, nullptr, // We don't have lighting, chunk will be lighted when needed @@ -3974,7 +3531,7 @@ void cWorld::cChunkGeneratorCallbacks::OnChunkGenerated(cChunkDesc & a_ChunkDesc std::move(a_ChunkDesc.GetEntities()), std::move(a_ChunkDesc.GetBlockEntities()), true )); - SetChunkData->RemoveInvalidBlockEntities(); + SetChunkData.RemoveInvalidBlockEntities(); m_World->QueueSetChunkData(SetChunkData); } diff --git a/src/World.h b/src/World.h index c1794b159..f6fbfae0f 100644 --- a/src/World.h +++ b/src/World.h @@ -28,7 +28,7 @@ #include "FastRandom.h" #include "ClientHandle.h" #include "EffectID.h" -#include <functional> +#include <future> @@ -39,9 +39,6 @@ class cRedstoneSimulator; class cItem; class cPlayer; class cClientHandle; -typedef SharedPtr<cClientHandle> cClientHandlePtr; -typedef std::list<cClientHandlePtr> cClientHandlePtrs; -typedef std::list<cClientHandle *> cClientHandles; class cEntity; class cBlockEntity; class cWorldGenerator; // The generator that actually generates the chunks for a single world @@ -59,12 +56,6 @@ class cCuboid; class cSetChunkData; class cBroadcaster; - -typedef std::list< cPlayer * > cPlayerList; - -typedef SharedPtr<cSetChunkData> cSetChunkDataPtr; // TODO: Change to unique_ptr once we go C++11 -typedef std::vector<cSetChunkDataPtr> cSetChunkDataPtrs; - typedef cItemCallback<cPlayer> cPlayerListCallback; typedef cItemCallback<cEntity> cEntityCallback; typedef cItemCallback<cBeaconEntity> cBeaconCallback; @@ -92,14 +83,9 @@ public: // tolua_end - /** A simple RAII locker for the chunkmap - locks the chunkmap in its constructor, unlocks it in the destructor */ - class cLock : - public cCSLock - { - typedef cCSLock super; - public: - cLock(cWorld & a_World); - }; + cWorld(const AString & a_WorldName, eDimension a_Dimension = dimOverworld, const AString & a_LinkedOverworldName = ""); + virtual ~cWorld(); + cWorld(const cWorld & a_World) = delete; static const char * GetClassStatic(void) // Needed for ManualBindings's ForEach templates { @@ -234,7 +220,7 @@ public: /** Puts the chunk data into a queue to be set into the chunkmap in the tick thread. If the chunk data doesn't contain valid biomes, the biomes are calculated before adding the data into the queue. */ - void QueueSetChunkData(const cSetChunkDataPtr & a_SetChunkData); + void QueueSetChunkData(cSetChunkData & a_SetChunkData); void ChunkLighted( int a_ChunkX, int a_ChunkZ, @@ -267,9 +253,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. */ - void RemovePlayer(cPlayer * a_Player, bool a_RemoveFromChunk); + 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. */ + void RemovePlayer(cPlayer * a_Player); /** Calls the callback for each player in the list; returns true if all players processed, false if the callback aborted by returning true */ virtual bool ForEachPlayer(cPlayerListCallback & a_Callback) override; // >> EXPORTED IN MANUALBINDINGS << @@ -316,24 +301,12 @@ public: void CompareChunkClients(int a_ChunkX1, int a_ChunkZ1, int a_ChunkX2, int a_ChunkZ2, cClientDiffCallback & a_Callback); /** Adds client to a chunk, if not already present; returns true if added, false if present */ - bool AddChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client); + bool AddChunkClient(int a_ChunkX, int a_ChunkZ, const std::shared_ptr<cClientHandle> & a_Client); /** Removes client from the chunk specified */ - void RemoveChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client); - - /** Removes the client from all chunks it is present in */ - void RemoveClientFromChunks(cClientHandle * a_Client); - - /** Sends the chunk to the client specified, if the client doesn't have the chunk yet. - If chunk not valid, the request is postponed (ChunkSender will send that chunk when it becomes valid + lighted). */ - void SendChunkTo(int a_ChunkX, int a_ChunkZ, cChunkSender::eChunkPriority a_Priority, cClientHandle * a_Client); + void RemoveChunkClient(int a_ChunkX, int a_ChunkZ, const std::shared_ptr<cClientHandle> & a_Client); - /** Sends the chunk to the client specified, even if the client already has the chunk. - If the chunk's not valid, the request is postponed (ChunkSender will send that chunk when it becomes valid + lighted). */ - void ForceSendChunkTo(int a_ChunkX, int a_ChunkZ, cChunkSender::eChunkPriority a_Priority, cClientHandle * a_Client); - - /** Removes client from ChunkSender's queue of chunks to be sent */ - void RemoveClientFromChunkSender(cClientHandle * a_Client); + cChunkSender & GetChunkSender(void) { return m_ChunkSender; } /** Touches the chunk, causing it to be loaded or generated */ void TouchChunk(int a_ChunkX, int a_ChunkZ); @@ -675,7 +648,13 @@ public: void QueueSaveAllChunks(void); // tolua_export /** Queues a task onto the tick thread. The task object will be deleted once the task is finished */ - void QueueTask(std::function<void(cWorld &)> a_Task); // Exported in ManualBindings.cpp + template <typename FunctionType> + std::future<void> QueueTask(FunctionType && a_Task) // Exported in ManualBindings.cpp + { + cCSLock Lock(m_CSTasks); + m_Tasks.emplace_back(-1, std::forward<FunctionType>(a_Task)); + return m_Tasks.back().second.get_future(); + } /** Queues a lambda task onto the tick thread, with the specified delay. */ void ScheduleTask(int a_DelayTicks, std::function<void(cWorld &)> a_Task); @@ -694,10 +673,16 @@ public: cLightingThread & GetLightingThread(void) { return m_Lighting; } - void InitializeSpawn(void); + /** Starts the tick thread that belongs to this world. */ + void Start(void) { m_TickThread.Start(); } - /** Starts threads that belong to this world */ - void Start(void); + /** Performs one-off initial configuration of the world. + MUST be called within the context of the world tick thread. */ + void Initialise(void); + + /** Performs one-off initial configuration of the world spawn. + MUST be called within the context of the world tick thread. */ + void InitialiseSpawn(void); /** Stops threads that belong to this world (part of deinit) */ void Stop(void); @@ -809,6 +794,14 @@ public: cBroadcaster GetBroadcaster(); +#ifdef _DEBUG + + /** Debug-only function to ensure the current execution is within the context of this world's tick thread + Enforces single-thread access requirements of cWorld, cChunkMap, cChunk, cEntity and descendants, etc. */ + bool IsInTickThread(void) { return m_TickThread.IsCurrentThread(); } + +#endif + private: friend class cRoot; @@ -846,8 +839,11 @@ private: public: cChunkGeneratorCallbacks(cWorld & a_World); - } ; + }; + /** Flag to indicate whether cWorld::Tick should be called in cTickThread::Execute. + MUST NOT be mutated by a caller other than cWorld::Stop. */ + bool m_ShouldTick; AString m_WorldName; @@ -909,8 +905,9 @@ private: std::unique_ptr<cFireSimulator> m_FireSimulator; cRedstoneSimulator * m_RedstoneSimulator; - cCriticalSection m_CSPlayers; - cPlayerList m_Players; + /** Stores pointers to all players in the world for easy access. + Players MUST be removed from this list through a call to RemovePlayer before they are destroyed. */ + std::vector<cPlayer *> m_Players; cWorldStorage m_Storage; @@ -982,41 +979,7 @@ private: cCriticalSection m_CSTasks; /** Tasks that have been queued onto the tick thread, possibly to be executed at target tick in the future; guarded by m_CSTasks */ - std::vector<std::pair<Int64, std::function<void(cWorld &)>>> m_Tasks; - - /** Guards m_Clients */ - cCriticalSection m_CSClients; - - /** List of clients in this world, these will be ticked by this world */ - cClientHandlePtrs m_Clients; - - /** Clients that are scheduled for removal (ticked in another world), waiting for TickClients() to remove them */ - cClientHandles m_ClientsToRemove; - - /** Clients that are scheduled for adding, waiting for TickClients to add them */ - cClientHandlePtrs m_ClientsToAdd; - - /** Guards m_EntitiesToAdd */ - cCriticalSection m_CSEntitiesToAdd; - - /** List of entities that are scheduled for adding, waiting for the Tick thread to add them. */ - cEntityList m_EntitiesToAdd; - - /** Guards m_PlayersToAdd */ - cCriticalSection m_CSPlayersToAdd; - - /** List of players that are scheduled for adding, waiting for the Tick thread to add them. */ - cPlayerList m_PlayersToAdd; - - /** CS protecting m_SetChunkDataQueue. */ - cCriticalSection m_CSSetChunkDataQueue; - - /** Queue for the chunk data to be set into m_ChunkMap by the tick thread. Protected by m_CSSetChunkDataQueue */ - cSetChunkDataPtrs m_SetChunkDataQueue; - - - cWorld(const AString & a_WorldName, eDimension a_Dimension = dimOverworld, const AString & a_LinkedOverworldName = ""); - virtual ~cWorld(); + std::vector<std::pair<Int64, std::packaged_task<void(cWorld &)>>> m_Tasks; void Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_LastTickDurationMSec); @@ -1029,24 +992,11 @@ private: /** Executes all tasks queued onto the tick thread */ void TickQueuedTasks(void); - /** Ticks all clients that are in this world */ - void TickClients(float a_Dt); - /** Unloads all chunks immediately. */ void UnloadUnusedChunks(void); void UpdateSkyDarkness(void); - /** Generates a random spawnpoint on solid land by walking chunks and finding their biomes */ - void GenerateRandomSpawn(int a_MaxSpawnRadius); - - /** Can the specified coordinates be used as a spawn point? - Returns true if spawn position is valid and sets a_Y to the valid spawn height */ - bool CanSpawnAt(double a_X, double & a_Y, double a_Z); - - /** Check if player starting point is acceptable */ - bool CheckPlayerSpawnPoint(int a_PosX, int a_PosY, int a_PosZ); - /** Chooses a reasonable transition from the current weather to a new weather */ eWeather ChooseNewWeather(void); @@ -1056,10 +1006,6 @@ private: /** Creates a new redstone simulator. */ cRedstoneSimulator * InitializeRedstoneSimulator(cIniFile & a_IniFile); - /** Adds the players queued in the m_PlayersToAdd queue into the m_Players list. - Assumes it is called from the Tick thread. */ - void AddQueuedPlayers(void); - /** Sets generator values to dimension specific defaults, if those values do not exist */ void InitialiseGeneratorDefaults(cIniFile & a_IniFile); diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index 4fc855589..1d81ac0b7 100755 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -459,7 +459,7 @@ bool cWSSAnvil::LoadChunkFromNBT(const cChunkCoords & a_Chunk, const cParsedNBT } // for y //*/ - cSetChunkDataPtr SetChunkData(new cSetChunkData( + auto SetChunkData(cSetChunkData( a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, BlockTypes, MetaData, IsLightValid ? BlockLight : nullptr, @@ -495,7 +495,16 @@ bool cWSSAnvil::SaveChunkToNBT(const cChunkCoords & a_Chunk, cFastNBTWriter & a_ a_Writer.AddInt("zPos", a_Chunk.m_ChunkZ); cNBTChunkSerializer Serializer(a_Writer); - if (!m_World->GetChunkData(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, Serializer)) + bool DataRetrivalSucceeded; + + m_World->QueueTask( + [&Serializer, &a_Chunk, &DataRetrivalSucceeded](cWorld & a_World) + { + DataRetrivalSucceeded = a_World.GetChunkData(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, Serializer); + } + ).wait(); + + if (!DataRetrivalSucceeded) { LOGWARNING("Cannot get chunk [%d, %d] data for NBT saving", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ); return false; diff --git a/src/WorldStorage/WorldStorage.cpp b/src/WorldStorage/WorldStorage.cpp index 55555d731..7c7bb4bfd 100644 --- a/src/WorldStorage/WorldStorage.cpp +++ b/src/WorldStorage/WorldStorage.cpp @@ -156,6 +156,7 @@ void cWorldStorage::QueueSaveChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallba { ASSERT(m_World->IsChunkValid(a_ChunkX, a_ChunkZ)); + m_World->MarkChunkSaving(a_ChunkX, a_ChunkZ); m_SaveQueue.EnqueueItem(cChunkCoordsWithCallback(a_ChunkX, a_ChunkZ, a_Callback)); m_Event.Set(); } @@ -259,16 +260,18 @@ bool cWorldStorage::SaveOneChunk(void) return false; } - // Save the chunk, if it's valid: + cChunkCoords SaveCoordinates(ToSave.m_ChunkX, ToSave.m_ChunkZ); bool Status = false; - if (m_World->IsChunkValid(ToSave.m_ChunkX, ToSave.m_ChunkZ)) + + // Save the chunk; validity is checked inside a cWorld task: + if (m_SaveSchema->SaveChunk(SaveCoordinates)) { - m_World->MarkChunkSaving(ToSave.m_ChunkX, ToSave.m_ChunkZ); - if (m_SaveSchema->SaveChunk(cChunkCoords(ToSave.m_ChunkX, ToSave.m_ChunkZ))) - { - m_World->MarkChunkSaved(ToSave.m_ChunkX, ToSave.m_ChunkZ); - Status = true; - } + Status = true; + m_World->QueueTask([SaveCoordinates](cWorld & a_World) + { + a_World.MarkChunkSaved(SaveCoordinates.m_ChunkX, SaveCoordinates.m_ChunkZ); + } + ); } // Call the callback, if specified: @@ -285,8 +288,6 @@ bool cWorldStorage::SaveOneChunk(void) bool cWorldStorage::LoadChunk(int a_ChunkX, int a_ChunkZ) { - ASSERT(m_World->IsChunkQueued(a_ChunkX, a_ChunkZ)); - cChunkCoords Coords(a_ChunkX, a_ChunkZ); // First try the schema that is used for saving @@ -305,7 +306,12 @@ bool cWorldStorage::LoadChunk(int a_ChunkX, int a_ChunkZ) } // Notify the chunk owner that the chunk failed to load (sets cChunk::m_HasLoadFailed to true): - m_World->ChunkLoadFailed(a_ChunkX, a_ChunkZ); + m_World->QueueTask( + [a_ChunkX, a_ChunkZ](cWorld & a_World) + { + a_World.ChunkLoadFailed(a_ChunkX, a_ChunkZ); + } + ); return false; } |