From 18f91ae940f5c5c86d3e4e93d243455c65f1ae97 Mon Sep 17 00:00:00 2001 From: Michael Serajnik Date: Sat, 28 Sep 2024 15:08:09 +0200 Subject: [PATCH 1/5] Add Node.js SOAP example. (#2784) --- contrib/soap/example.js | 82 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 contrib/soap/example.js diff --git a/contrib/soap/example.js b/contrib/soap/example.js new file mode 100644 index 00000000000..330d1119553 --- /dev/null +++ b/contrib/soap/example.js @@ -0,0 +1,82 @@ +/*! + * Copyright (C) 2024-present VMaNGOS https://github.com/vmangos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Using https://www.npmjs.com/package/soap does not work because it expects +// a WSDL file, which we don't have. Instead, we can use a regular HTTP client +// (like the native `fetch` API which is stable since Node.js v21) to construct +// and send SOAP requests manually. + +// Optional: +// We can replace XML entities with their respective characters by installing +// https://www.npmjs.com/package/entities: +// const entities = require('entities') + +const username = 'ADMINISTRATOR' +const password = 'ADMINISTRATOR' +const host = 'localhost' +const port = 7878 +const command = 'server info' + +const buildSoapRequest = command => ` + + + + ${command} + + + +` + +const soapRequest = buildSoapRequest(command) + +;(async () => { + try { + const response = await fetch(`http://${host}:${port}`, { + method: 'POST', + headers: { + 'Content-Type': 'text/xml', + 'SOAPAction': 'urn:MaNGOS', + 'Authorization': 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64') + }, + body: soapRequest + }) + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`) + } + + const rawResult = await response.text() + + if (response.status !== 200) { + console.warn(`Unexpected status code: ${response.status}`) + console.warn('Raw output:', rawResult) + return + } + + const resultBeginIdx = rawResult.indexOf('') + ''.length + const resultEndIdx = rawResult.lastIndexOf('') + let result = rawResult.slice(resultBeginIdx, resultEndIdx) + // If we chose to install the `entities` package, we can decode XML + // entities: + // result = entities.decodeXML(result) + + console.log('Command succeeded! Output:', result) + } catch (error) { + console.error('Command failed! Reason:', error.message) + } +})() From 93bb983d546961e95d67982db174ed18595b672d Mon Sep 17 00:00:00 2001 From: ratkosrb Date: Sat, 28 Sep 2024 16:29:36 +0300 Subject: [PATCH 2/5] Fix hang on shutdown. --- src/game/World.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/game/World.cpp b/src/game/World.cpp index 7d552e29aa6..6a82da38c99 100644 --- a/src/game/World.cpp +++ b/src/game/World.cpp @@ -1941,11 +1941,15 @@ void World::DetectDBCLang() // Only processes packets while session update, the messager, and cli commands processing are NOT running void World::ProcessAsyncPackets() { - while (!sWorld.IsStopped()) + while (!IsStopped()) { do { std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + if (IsStopped()) + return; + } while (!m_canProcessAsyncPackets); for (auto const& itr : m_sessions) From a9321a4ec925d80b78bd6203611ce96e85ecae6a Mon Sep 17 00:00:00 2001 From: ratkosrb Date: Sat, 28 Sep 2024 16:43:01 +0300 Subject: [PATCH 3/5] Use threadpool for CreateNewInstancesForPlayers. --- src/game/Maps/MapManager.cpp | 13 +++++++++---- src/game/Maps/MapManager.h | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/game/Maps/MapManager.cpp b/src/game/Maps/MapManager.cpp index d8a46537b02..0695fc84754 100644 --- a/src/game/Maps/MapManager.cpp +++ b/src/game/Maps/MapManager.cpp @@ -42,10 +42,12 @@ MapManager::MapManager() : i_gridCleanUpDelay(sWorld.getConfig(CONFIG_UINT32_INTERVAL_GRIDCLEAN)), i_MaxInstanceId(RESERVED_INSTANCES_LAST), - m_threads(new ThreadPool(sWorld.getConfig(CONFIG_UINT32_MAPUPDATE_INSTANCED_UPDATE_THREADS))) + m_threads(new ThreadPool(sWorld.getConfig(CONFIG_UINT32_MAPUPDATE_INSTANCED_UPDATE_THREADS))), + m_instanceCreationThreads(new ThreadPool(1)) { i_timer.SetInterval(sWorld.getConfig(CONFIG_UINT32_INTERVAL_MAPUPDATE)); m_threads->start>(); + m_instanceCreationThreads->start<>(); } MapManager::~MapManager() @@ -336,7 +338,10 @@ void MapManager::Update(uint32 diff) } } - std::thread instanceCreationThread = std::thread(&MapManager::CreateNewInstancesForPlayers, this); + std::vector> instanceCreators; + instanceCreators.emplace_back([this]() {CreateNewInstancesForPlayers();}); + std::future instances = m_instanceCreationThreads->processWorkload(std::move(instanceCreators), + ThreadPool::Callable()); i_maxContinentThread = continentsIdx; i_continentUpdateFinished.store(0); @@ -367,8 +372,8 @@ void MapManager::Update(uint32 diff) SwitchPlayersInstances(); asyncMapUpdating = false; - if (instanceCreationThread.joinable()) - instanceCreationThread.join(); + if (instances.valid()) + instances.wait(); // Execute far teleports after all map updates have finished ExecuteDelayedPlayerTeleports(); diff --git a/src/game/Maps/MapManager.h b/src/game/Maps/MapManager.h index 54f526cffc0..e3a4aa1f9e8 100644 --- a/src/game/Maps/MapManager.h +++ b/src/game/Maps/MapManager.h @@ -225,6 +225,7 @@ class MapManager : public MaNGOS::Singleton m_threads; std::unique_ptr m_continentThreads; + std::unique_ptr m_instanceCreationThreads; bool asyncMapUpdating = false; // Instanced continent zones From b829db5f61777bafd54262edfcdc8f42407c27e0 Mon Sep 17 00:00:00 2001 From: ratkosrb Date: Sat, 28 Sep 2024 17:44:50 +0300 Subject: [PATCH 4/5] Load dungeon map grids before adding the player. This should speed up MSG_MOVE_WORLDPORT_ACK handling a lot. --- src/game/Maps/Map.cpp | 12 ++++++++++++ src/game/Maps/Map.h | 1 + src/game/Maps/MapManager.cpp | 1 + src/game/Objects/Object.cpp | 7 ------- src/game/Objects/Object.h | 7 +++++++ 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/game/Maps/Map.cpp b/src/game/Maps/Map.cpp index aca3c22401c..b13cfa0d083 100644 --- a/src/game/Maps/Map.cpp +++ b/src/game/Maps/Map.cpp @@ -392,6 +392,18 @@ bool Map::EnsureGridLoaded(Cell const& cell) return false; } +void Map::ForceLoadGridsAroundPosition(float x, float y) +{ + if (!IsLoaded(x, y)) + { + CellPair p = MaNGOS::ComputeCellPair(x, y); + Cell cell(p); + EnsureGridLoadedAtEnter(cell); + NULLNotifier notifier = NULLNotifier(); + Cell::VisitAllObjects(x, y, this, notifier, GetGridActivationDistance(), false); + } +} + void Map::LoadGrid(Cell const& cell, bool no_unload) { EnsureGridLoaded(cell); diff --git a/src/game/Maps/Map.h b/src/game/Maps/Map.h index 731618713e8..8b249793577 100644 --- a/src/game/Maps/Map.h +++ b/src/game/Maps/Map.h @@ -393,6 +393,7 @@ class Map : public GridRefManager bool GetUnloadLock(GridPair const& p) const { return getNGrid(p.x_coord, p.y_coord)->getUnloadLock(); } void SetUnloadLock(GridPair const& p, bool on) { getNGrid(p.x_coord, p.y_coord)->setUnloadExplicitLock(on); } + void ForceLoadGridsAroundPosition(float x, float y); void LoadGrid(Cell const& cell, bool no_unload = false); bool UnloadGrid(uint32 const& x, uint32 const& y, bool pForce); virtual void UnloadAll(bool pForce); diff --git a/src/game/Maps/MapManager.cpp b/src/game/Maps/MapManager.cpp index 0695fc84754..836b5e80039 100644 --- a/src/game/Maps/MapManager.cpp +++ b/src/game/Maps/MapManager.cpp @@ -277,6 +277,7 @@ void MapManager::CreateNewInstancesForPlayers() DungeonMap* pMap = static_cast(CreateInstance(dest.mapId, player)); if (pMap->CanEnter(player)) { + pMap->ForceLoadGridsAroundPosition(dest.x, dest.y); pMap->BindPlayerOrGroupOnEnter(player); player->SendNewWorld(); } diff --git a/src/game/Objects/Object.cpp b/src/game/Objects/Object.cpp index a5d053b9fac..07920f3574c 100644 --- a/src/game/Objects/Object.cpp +++ b/src/game/Objects/Object.cpp @@ -3460,13 +3460,6 @@ void WorldObject::Update(uint32 update_diff, uint32 /*time_diff*/) ExecuteDelayedActions(); } -class NULLNotifier -{ -public: - template void Visit(GridRefManager& m) {} - void Visit(CameraMapType&) {} -}; - void WorldObject::LoadMapCellsAround(float dist) const { ASSERT(IsInWorld()); diff --git a/src/game/Objects/Object.h b/src/game/Objects/Object.h index 0a135fb708a..6653e6447f5 100644 --- a/src/game/Objects/Object.h +++ b/src/game/Objects/Object.h @@ -56,6 +56,13 @@ class GenericTransport; struct FactionEntry; struct FactionTemplateEntry; +class NULLNotifier +{ +public: + template void Visit(GridRefManager& m) {} + void Visit(CameraMapType&) {} +}; + typedef std::unordered_map UpdateDataMapType; //use this class to measure time between world update ticks From d1f0c119744c1d3c7c3a05eb5a0635f93d4d1ebc Mon Sep 17 00:00:00 2001 From: ratkosrb Date: Sat, 28 Sep 2024 19:07:01 +0300 Subject: [PATCH 5/5] Guard async packets processing behind mutex. --- src/game/World.cpp | 48 ++++++++++++++++++++++++++++------------------ src/game/World.h | 1 + 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/game/World.cpp b/src/game/World.cpp index 6a82da38c99..669263c237c 100644 --- a/src/game/World.cpp +++ b/src/game/World.cpp @@ -1941,16 +1941,16 @@ void World::DetectDBCLang() // Only processes packets while session update, the messager, and cli commands processing are NOT running void World::ProcessAsyncPackets() { - while (!IsStopped()) + do { - do - { - std::this_thread::sleep_for(std::chrono::milliseconds(20)); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + std::lock_guard lock(m_asyncPacketsMutex); - if (IsStopped()) - return; + if (IsStopped()) + return; - } while (!m_canProcessAsyncPackets); + if (!m_canProcessAsyncPackets) + continue; for (auto const& itr : m_sessions) { @@ -1963,7 +1963,7 @@ void World::ProcessAsyncPackets() if (!m_canProcessAsyncPackets) break; } - } + } while (!IsStopped()); } // Update the World ! @@ -1998,16 +1998,21 @@ void World::Update(uint32 diff) sAuctionMgr.Update(); } - GetMessager().Execute(this); + { + m_canProcessAsyncPackets = false; + std::lock_guard lock(m_asyncPacketsMutex); + + GetMessager().Execute(this); - //
  • Handle session updates - uint32 updateSessionsTime = WorldTimer::getMSTime(); - UpdateSessions(diff); - updateSessionsTime = WorldTimer::getMSTimeDiffToNow(updateSessionsTime); - if (getConfig(CONFIG_UINT32_PERFLOG_SLOW_SESSIONS_UPDATE) && updateSessionsTime > getConfig(CONFIG_UINT32_PERFLOG_SLOW_SESSIONS_UPDATE)) - sLog.Out(LOG_PERFORMANCE, LOG_LVL_MINIMAL, "Update sessions: %ums", updateSessionsTime); + //
  • Handle session updates + uint32 updateSessionsTime = WorldTimer::getMSTime(); + UpdateSessions(diff); + updateSessionsTime = WorldTimer::getMSTimeDiffToNow(updateSessionsTime); + if (getConfig(CONFIG_UINT32_PERFLOG_SLOW_SESSIONS_UPDATE) && updateSessionsTime > getConfig(CONFIG_UINT32_PERFLOG_SLOW_SESSIONS_UPDATE)) + sLog.Out(LOG_PERFORMANCE, LOG_LVL_MINIMAL, "Update sessions: %ums", updateSessionsTime); - m_canProcessAsyncPackets = true; + m_canProcessAsyncPackets = true; + } //
  • Update uptime table if (m_timers[WUPDATE_UPTIME].Passed()) @@ -2117,10 +2122,15 @@ void World::Update(uint32 diff) // Update ban list if necessary sAccountMgr.Update(diff); - m_canProcessAsyncPackets = false; + { + m_canProcessAsyncPackets = false; + std::lock_guard lock(m_asyncPacketsMutex); - // And last, but not least handle the issued cli commands - ProcessCliCommands(); + // And last, but not least handle the issued cli commands + ProcessCliCommands(); + + m_canProcessAsyncPackets = true; + } //cleanup unused GridMap objects as well as VMaps if (getConfig(CONFIG_BOOL_CLEANUP_TERRAIN)) diff --git a/src/game/World.h b/src/game/World.h index 1aa81f2fa17..f10ce38ac16 100644 --- a/src/game/World.h +++ b/src/game/World.h @@ -1022,6 +1022,7 @@ class World // This thread handles packets while the world sessions update is not running std::unique_ptr m_asyncPacketsThread; + std::mutex m_asyncPacketsMutex; bool m_canProcessAsyncPackets; void ProcessAsyncPackets();