From 3247437ef979a56b41b87ef89450e90a27df138b Mon Sep 17 00:00:00 2001 From: Mohab <133429578+MohabCodeX@users.noreply.github.com> Date: Fri, 5 Sep 2025 16:35:50 +0300 Subject: [PATCH 1/2] Implement bulk tree deletion for elements and add CEntityRemoveTreePacket --- .../mods/deathmatch/logic/CPacketHandler.cpp | 112 ++++++++++++++++++ Client/mods/deathmatch/logic/CPacketHandler.h | 5 + .../mods/deathmatch/logic/CElementDeleter.cpp | 66 +++++++++-- .../mods/deathmatch/logic/CElementDeleter.h | 10 +- Server/mods/deathmatch/logic/CResource.cpp | 19 +-- .../logic/packets/CEntityRemoveTreePacket.cpp | 28 +++++ .../logic/packets/CEntityRemoveTreePacket.h | 31 +++++ Shared/sdk/net/Packets.h | 3 +- 8 files changed, 252 insertions(+), 22 deletions(-) create mode 100644 Server/mods/deathmatch/logic/packets/CEntityRemoveTreePacket.cpp create mode 100644 Server/mods/deathmatch/logic/packets/CEntityRemoveTreePacket.h diff --git a/Client/mods/deathmatch/logic/CPacketHandler.cpp b/Client/mods/deathmatch/logic/CPacketHandler.cpp index 7dace41f4fa..1850513d160 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.cpp +++ b/Client/mods/deathmatch/logic/CPacketHandler.cpp @@ -10,6 +10,7 @@ *****************************************************************************/ #include "StdInc.h" +#include #include #include #include @@ -135,6 +136,11 @@ bool CPacketHandler::ProcessPacket(unsigned char ucPacketID, NetBitStreamInterfa Packet_EntityRemove(bitStream); return true; + // Bulk delete element trees + case PACKET_ID_ENTITY_REMOVE_TREE: + Packet_EntityRemoveTree(bitStream); + return true; + // Respawns/hides pickups case PACKET_ID_PICKUP_HIDESHOW: Packet_PickupHideShow(bitStream); @@ -4348,6 +4354,112 @@ void CPacketHandler::Packet_EntityRemove(NetBitStreamInterface& bitStream) } } +void CPacketHandler::Packet_EntityRemoveTree(NetBitStreamInterface& bitStream) +{ + // std::uint16_t (2) - number of root elements + // ElementID (2) - root element ids (repeating) + + std::uint16_t rootElementCount; + if (!bitStream.ReadCompressed(rootElementCount)) + return; + + std::vector rootElements; + rootElements.reserve(rootElementCount); + + // Read all root element IDs first + for (std::uint16_t i = 0; i < rootElementCount; ++i) + { + ElementID rootID; + if (!bitStream.Read(rootID)) + return; + + CClientEntity* pRootEntity = CElementIDs::GetElement(rootID); + if (pRootEntity) + { + if (pRootEntity->GetType() == CCLIENTPLAYER) + { + RaiseProtocolError(45); + return; + } + rootElements.push_back(pRootEntity); + } + } + + // Delete each tree hierarchically + for (auto* pRootEntity : rootElements) + { + RemoveEntityTree(pRootEntity); + } +} + +void CPacketHandler::RemoveEntityTree(CClientEntity* rootEntity) +{ + if (!rootEntity || rootEntity->IsSystemEntity()) + return; + + // Collect all entities in this tree + std::vector entitiesToDelete; + CollectEntityTree(rootEntity, entitiesToDelete); + + // Handle vehicle/ped specific cleanup for each entity + CMappedList listOfPeds(g_pClientGame->GetPedSync()->GetList()); + listOfPeds.push_front(g_pClientGame->GetLocalPlayer()); + + for (auto* entity : entitiesToDelete) + { + if (entity->GetType() == CCLIENTVEHICLE || entity->GetType() == CCLIENTPED) + { + ElementID entityID = entity->GetID(); + + if (entity->GetType() == CCLIENTVEHICLE) + { + for (auto* pPed : listOfPeds) + { + if (pPed->m_VehicleInOutID == entityID) + pPed->ResetVehicleInOut(); + + if (pPed->m_bNoNewVehicleTask && pPed->m_NoNewVehicleTaskReasonID == entityID) + { + pPed->m_bNoNewVehicleTask = false; + pPed->m_NoNewVehicleTaskReasonID = INVALID_ELEMENT_ID; + } + } + } + else + { + auto* removedPed = static_cast(entity); + for (auto* pPed : listOfPeds) + { + if (pPed->m_bIsGettingJacked && pPed->m_pGettingJackedBy == removedPed) + { + pPed->ResetVehicleInOut(); + pPed->RemoveFromVehicle(false); + pPed->SetVehicleInOutState(VEHICLE_INOUT_NONE); + } + } + } + } + + // Delete clientside children and the entity + entity->DeleteClientChildren(); + g_pClientGame->m_ElementDeleter.Delete(entity); + } +} + +void CPacketHandler::CollectEntityTree(CClientEntity* entity, std::vector& entities) +{ + if (!entity) + return; + + entities.push_back(entity); + + // Recursively collect all children + for (auto iter = entity->IterBegin(); iter != entity->IterEnd(); ++iter) + { + CollectEntityTree(*iter, entities); + } +} + void CPacketHandler::Packet_PickupHideShow(NetBitStreamInterface& bitStream) { // bool - show it? diff --git a/Client/mods/deathmatch/logic/CPacketHandler.h b/Client/mods/deathmatch/logic/CPacketHandler.h index 9c8de37454b..e310432be1d 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.h +++ b/Client/mods/deathmatch/logic/CPacketHandler.h @@ -78,6 +78,7 @@ class CPacketHandler void Packet_PlayerNetworkStatus(NetBitStreamInterface& bitStream); void Packet_EntityAdd(NetBitStreamInterface& bitStream); void Packet_EntityRemove(NetBitStreamInterface& bitStream); + void Packet_EntityRemoveTree(NetBitStreamInterface& bitStream); void Packet_PickupHideShow(NetBitStreamInterface& bitStream); void Packet_PickupHitConfirm(NetBitStreamInterface& bitStream); void Packet_Lua(unsigned char ucPacketID, NetBitStreamInterface& bitStream); @@ -114,5 +115,9 @@ class CPacketHandler NetBitStreamInterface* m_pEntityAddBitStream; uint m_uiEntityAddNumEntities; +private: + void RemoveEntityTree(class CClientEntity* rootEntity); + void CollectEntityTree(class CClientEntity* entity, std::vector& entities); + std::list> m_displayTextList; }; diff --git a/Server/mods/deathmatch/logic/CElementDeleter.cpp b/Server/mods/deathmatch/logic/CElementDeleter.cpp index c45f7e3873e..a1e402eae91 100644 --- a/Server/mods/deathmatch/logic/CElementDeleter.cpp +++ b/Server/mods/deathmatch/logic/CElementDeleter.cpp @@ -51,19 +51,69 @@ void CElementDeleter::DoDeleteAll() delete *m_List.begin(); } -void CElementDeleter::Unreference(CElement* pElement) +void CElementDeleter::Unreference(CElement* element) { - m_List.remove(pElement); + m_List.remove(element); } -bool CElementDeleter::IsBeingDeleted(CElement* pElement) +bool CElementDeleter::IsBeingDeleted(CElement* element) const { - return ListContains(m_List, pElement); + return ListContains(m_List, element); } -void CElementDeleter::CleanUpForVM(CLuaMain* pLuaMain) +void CElementDeleter::DeleteTree(CElement* rootElement, bool unlink, bool updatePerPlayerEntities) { - CElementListType::const_iterator iter = m_List.begin(); - for (; iter != m_List.end(); iter++) - (*iter)->DeleteEvents(pLuaMain, false); + if (!rootElement || IsBeingDeleted(rootElement)) + return; + + std::vector elementsToDelete; + CollectTreeElements(rootElement, elementsToDelete); + + // Fire destroy events for all elements + for (auto* element : elementsToDelete) + { + if (IsBeingDeleted(element)) + continue; + + CLuaArguments arguments; + element->CallEvent("onElementDestroy", arguments); + + if (!element->IsBeingDeleted()) + m_List.push_back(element); + + g_pGame->GetPlayerManager()->ClearElementData(element); + element->SetIsBeingDeleted(true); + } + + // Clear children and unlink elements + for (auto* element : elementsToDelete) + { + element->ClearChildren(); + element->SetParentObject(nullptr, false); + + if (unlink) + element->Unlink(); + } + + if (updatePerPlayerEntities && rootElement) + rootElement->UpdatePerPlayerEntities(); +} + +void CElementDeleter::CollectTreeElements(CElement* element, std::vector& elements) +{ + if (!element || IsBeingDeleted(element)) + return; + + elements.push_back(element); + + for (auto iter = element->IterBegin(); iter != element->IterEnd(); ++iter) + { + CollectTreeElements(*iter, elements); + } +} + +void CElementDeleter::CleanUpForVM(CLuaMain* luaMain) +{ + for (auto* element : m_List) + element->DeleteEvents(luaMain, false); } diff --git a/Server/mods/deathmatch/logic/CElementDeleter.h b/Server/mods/deathmatch/logic/CElementDeleter.h index 254996c44a2..9afb63ffd2a 100644 --- a/Server/mods/deathmatch/logic/CElementDeleter.h +++ b/Server/mods/deathmatch/logic/CElementDeleter.h @@ -13,6 +13,7 @@ #include "CElement.h" #include +#include class CElementDeleter { @@ -20,13 +21,14 @@ class CElementDeleter ~CElementDeleter() { DoDeleteAll(); }; void Delete(class CElement* pElement, bool bUnlink = true, bool bUpdatePerPlayerEntities = true); + void DeleteTree(CElement* rootElement, bool unlink = true, bool updatePerPlayerEntities = true); void DoDeleteAll(); - bool IsBeingDeleted(class CElement* pElement); - void Unreference(CElement* pElement); - - void CleanUpForVM(CLuaMain* pLuaMain); + bool IsBeingDeleted(CElement* element) const; + void Unreference(CElement* element); + void CleanUpForVM(CLuaMain* luaMain); private: + void CollectTreeElements(CElement* element, std::vector& elements); CElementListType m_List; }; diff --git a/Server/mods/deathmatch/logic/CResource.cpp b/Server/mods/deathmatch/logic/CResource.cpp index fa1fe5c2f53..1666c87ceb1 100644 --- a/Server/mods/deathmatch/logic/CResource.cpp +++ b/Server/mods/deathmatch/logic/CResource.cpp @@ -31,6 +31,7 @@ #include "CHTTPD.h" #include "Utils.h" #include "packets/CResourceClientScriptsPacket.h" +#include "packets/CEntityRemoveTreePacket.h" #include "lua/CLuaFunctionParseHelpers.h" #include #include @@ -1171,26 +1172,26 @@ bool CResource::Stop(bool bManualStop) // Destroy the virtual machine for this resource DestroyVM(); - // Remove the resource element from the client - CEntityRemovePacket removePacket; + // Remove resource elements using bulk tree deletion + CEntityRemoveTreePacket removeTreePacket; if (m_pResourceElement) { - removePacket.Add(m_pResourceElement); - g_pGame->GetElementDeleter()->Delete(m_pResourceElement); + removeTreePacket.AddRootElement(m_pResourceElement); + g_pGame->GetElementDeleter()->DeleteTree(m_pResourceElement); m_pResourceElement = nullptr; } - // Remove the dynamic resource element from the client if (m_pResourceDynamicElementRoot) { - removePacket.Add(m_pResourceDynamicElementRoot); - g_pGame->GetElementDeleter()->Delete(m_pResourceDynamicElementRoot); + removeTreePacket.AddRootElement(m_pResourceDynamicElementRoot); + g_pGame->GetElementDeleter()->DeleteTree(m_pResourceDynamicElementRoot); m_pResourceDynamicElementRoot = nullptr; } - // Broadcast the packet to joined players - g_pGame->GetPlayerManager()->BroadcastOnlyJoined(removePacket); + // Broadcast single packet for entire resource cleanup + if (!removeTreePacket.IsEmpty()) + g_pGame->GetPlayerManager()->BroadcastOnlyJoined(removeTreePacket); // Clear the list of players where this resource is running std::exchange(m_isRunningForPlayer, {}); diff --git a/Server/mods/deathmatch/logic/packets/CEntityRemoveTreePacket.cpp b/Server/mods/deathmatch/logic/packets/CEntityRemoveTreePacket.cpp new file mode 100644 index 00000000000..dc41744db64 --- /dev/null +++ b/Server/mods/deathmatch/logic/packets/CEntityRemoveTreePacket.cpp @@ -0,0 +1,28 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include "CEntityRemoveTreePacket.h" +#include "CElement.h" +#include + +bool CEntityRemoveTreePacket::Write(NetBitStreamInterface& bitStream) const +{ + if (m_rootElements.empty()) + return false; + + bitStream.WriteCompressed(static_cast(m_rootElements.size())); + + for (const auto* element : m_rootElements) + { + bitStream.Write(element->GetID()); + } + + return true; +} diff --git a/Server/mods/deathmatch/logic/packets/CEntityRemoveTreePacket.h b/Server/mods/deathmatch/logic/packets/CEntityRemoveTreePacket.h new file mode 100644 index 00000000000..413a562a567 --- /dev/null +++ b/Server/mods/deathmatch/logic/packets/CEntityRemoveTreePacket.h @@ -0,0 +1,31 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include "CPacket.h" +#include +#include + +class CEntityRemoveTreePacket final : public CPacket +{ +public: + ePacketID GetPacketID() const noexcept override { return PACKET_ID_ENTITY_REMOVE_TREE; } + unsigned long GetFlags() const noexcept override { return PACKET_HIGH_PRIORITY | PACKET_RELIABLE | PACKET_SEQUENCED; } + + bool Write(NetBitStreamInterface& bitStream) const override; + + void AddRootElement(CElement* element) { m_rootElements.push_back(element); } + void Clear() noexcept { m_rootElements.clear(); } + bool IsEmpty() const noexcept { return m_rootElements.empty(); } + std::size_t GetRootElementCount() const noexcept { return m_rootElements.size(); } + +private: + std::vector m_rootElements; +}; diff --git a/Shared/sdk/net/Packets.h b/Shared/sdk/net/Packets.h index 2f587cad140..a79376faf55 100644 --- a/Shared/sdk/net/Packets.h +++ b/Shared/sdk/net/Packets.h @@ -177,5 +177,6 @@ enum ePacketID PACKET_ID_SERVER_INFO_SYNC, PACKET_ID_DISCORD_JOIN, PACKET_ID_PLAYER_RESOURCE_START, - PACKET_ID_PLAYER_WORLD_SPECIAL_PROPERTY + PACKET_ID_PLAYER_WORLD_SPECIAL_PROPERTY, + PACKET_ID_ENTITY_REMOVE_TREE }; From 549f2782225164d39457d5a2f1f1028466df1ae4 Mon Sep 17 00:00:00 2001 From: Mohab <133429578+MohabCodeX@users.noreply.github.com> Date: Fri, 5 Sep 2025 19:06:47 +0300 Subject: [PATCH 2/2] Refactor --- .../mods/deathmatch/logic/CPacketHandler.cpp | 36 +++++++++---------- Client/mods/deathmatch/logic/CPacketHandler.h | 4 +-- .../logic/packets/CEntityRemoveTreePacket.h | 1 - 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/Client/mods/deathmatch/logic/CPacketHandler.cpp b/Client/mods/deathmatch/logic/CPacketHandler.cpp index e93f5dfc8d6..6df093c2009 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.cpp +++ b/Client/mods/deathmatch/logic/CPacketHandler.cpp @@ -4384,22 +4384,22 @@ void CPacketHandler::Packet_EntityRemoveTree(NetBitStreamInterface& bitStream) if (!bitStream.Read(rootID)) return; - CClientEntity* pRootEntity = CElementIDs::GetElement(rootID); - if (pRootEntity) + CClientEntity* rootEntity = CElementIDs::GetElement(rootID); + if (rootEntity) { - if (pRootEntity->GetType() == CCLIENTPLAYER) + if (rootEntity->GetType() == CCLIENTPLAYER) { - RaiseProtocolError(45); + RaiseProtocolError(45); // Same as individual entity removal - cannot remove players return; } - rootElements.push_back(pRootEntity); + rootElements.push_back(rootEntity); } } // Delete each tree hierarchically - for (auto* pRootEntity : rootElements) + for (auto* rootEntity : rootElements) { - RemoveEntityTree(pRootEntity); + RemoveEntityTree(rootEntity); } } @@ -4424,28 +4424,28 @@ void CPacketHandler::RemoveEntityTree(CClientEntity* rootEntity) if (entity->GetType() == CCLIENTVEHICLE) { - for (auto* pPed : listOfPeds) + for (auto* ped : listOfPeds) { - if (pPed->m_VehicleInOutID == entityID) - pPed->ResetVehicleInOut(); + if (ped->m_VehicleInOutID == entityID) + ped->ResetVehicleInOut(); - if (pPed->m_bNoNewVehicleTask && pPed->m_NoNewVehicleTaskReasonID == entityID) + if (ped->m_bNoNewVehicleTask && ped->m_NoNewVehicleTaskReasonID == entityID) { - pPed->m_bNoNewVehicleTask = false; - pPed->m_NoNewVehicleTaskReasonID = INVALID_ELEMENT_ID; + ped->m_bNoNewVehicleTask = false; + ped->m_NoNewVehicleTaskReasonID = INVALID_ELEMENT_ID; } } } else { auto* removedPed = static_cast(entity); - for (auto* pPed : listOfPeds) + for (auto* ped : listOfPeds) { - if (pPed->m_bIsGettingJacked && pPed->m_pGettingJackedBy == removedPed) + if (ped->m_bIsGettingJacked && ped->m_pGettingJackedBy == removedPed) { - pPed->ResetVehicleInOut(); - pPed->RemoveFromVehicle(false); - pPed->SetVehicleInOutState(VEHICLE_INOUT_NONE); + ped->ResetVehicleInOut(); + ped->RemoveFromVehicle(false); + ped->SetVehicleInOutState(VEHICLE_INOUT_NONE); } } } diff --git a/Client/mods/deathmatch/logic/CPacketHandler.h b/Client/mods/deathmatch/logic/CPacketHandler.h index e310432be1d..6f85d98a0e7 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.h +++ b/Client/mods/deathmatch/logic/CPacketHandler.h @@ -116,8 +116,8 @@ class CPacketHandler uint m_uiEntityAddNumEntities; private: - void RemoveEntityTree(class CClientEntity* rootEntity); - void CollectEntityTree(class CClientEntity* entity, std::vector& entities); + void RemoveEntityTree(CClientEntity* rootEntity); + void CollectEntityTree(CClientEntity* entity, std::vector& entities); std::list> m_displayTextList; }; diff --git a/Server/mods/deathmatch/logic/packets/CEntityRemoveTreePacket.h b/Server/mods/deathmatch/logic/packets/CEntityRemoveTreePacket.h index 413a562a567..158ab3ad631 100644 --- a/Server/mods/deathmatch/logic/packets/CEntityRemoveTreePacket.h +++ b/Server/mods/deathmatch/logic/packets/CEntityRemoveTreePacket.h @@ -11,7 +11,6 @@ #include "CPacket.h" #include -#include class CEntityRemoveTreePacket final : public CPacket {