TrinityCore
Loading...
Searching...
No Matches
DynamicMMapTileBuilder.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
19#include "Containers.h"
20#include "DB2Stores.h"
21#include "DeadlineTimer.h"
22#include "GameObjectModel.h"
23#include "GameTime.h"
24#include "Hash.h"
25#include "IoContext.h"
26#include "Log.h"
27#include "MMapDefines.h"
28#include "MMapManager.h"
29#include "Map.h"
30#include "VMapFactory.h"
31#include "VMapManager.h"
32#include "World.h"
33#include "advstd.h"
34#include <thread>
35
36namespace
37{
38struct TileCacheKeyObject
39{
40 uint32 DisplayId;
42 std::array<int16, 3> Position;
43 int64 Rotation;
44
45 friend std::strong_ordering operator<=>(TileCacheKeyObject const&, TileCacheKeyObject const&) = default;
46 friend bool operator==(TileCacheKeyObject const&, TileCacheKeyObject const&) = default;
47};
48
49struct TileCacheKey
50{
51 uint32 TerrainMapId;
52 uint32 X;
53 uint32 Y;
54 std::size_t CachedHash; // computing the hash is expensive - store it
55 std::vector<TileCacheKeyObject> Objects;
56
57 friend bool operator==(TileCacheKey const&, TileCacheKey const&) = default;
58};
59}
60
61template <>
62struct std::hash<TileCacheKey>
63{
64 static std::size_t Compute(TileCacheKey const& key) noexcept
65 {
67 hash.UpdateData(key.TerrainMapId);
68 hash.UpdateData(key.X);
69 hash.UpdateData(key.Y);
70 for (TileCacheKeyObject const& object : key.Objects)
71 {
72 hash.UpdateData(object.DisplayId);
73 hash.UpdateData(object.Scale);
74 hash.UpdateData(object.Position);
75 hash.UpdateData(object.Rotation);
76 }
77 return hash.Value;
78 }
79
80 std::size_t operator()(TileCacheKey const& key) const noexcept
81 {
82 return key.CachedHash;
83 }
84};
85
86namespace
87{
88std::unique_ptr<VMAP::VMapManager> CreateVMapManager(uint32 mapId)
89{
90 std::unique_ptr<VMAP::VMapManager> vmgr = std::make_unique<VMAP::VMapManager>();
91
92 do
93 {
94 MapEntry const* mapEntry = sMapStore.AssertEntry(mapId);
95 if (!mapEntry)
96 break;
97
98 vmgr->InitializeThreadUnsafe(mapId, mapEntry->ParentMapID);
99 if (mapEntry->ParentMapID < 0)
100 break;
101
102 mapId = mapEntry->ParentMapID;
103 } while (true);
104
106 vmgr->GetLiquidFlagsPtr = globalManager->GetLiquidFlagsPtr;
107 vmgr->IsVMAPDisabledForPtr = globalManager->IsVMAPDisabledForPtr;
108 vmgr->LoadPathOnlyModels = globalManager->LoadPathOnlyModels;
109 return vmgr;
110}
111
112struct TileCache
113{
114 static TileCache* Instance()
115 {
116 static TileCache tc;
117 return &tc;
118 }
119
120 struct Tile
121 {
122 std::shared_ptr<MMAP::DynamicTileBuilder::AsyncTileResult> Data;
123 TimePoint LastAccessed;
124 };
125
126 static constexpr TimePoint::duration CACHE_CLEANUP_INTERVAL = 5min;
127 static constexpr TimePoint::duration CACHE_MAX_AGE = 30min;
128
129 std::mutex TilesMutex;
130 std::unordered_map<TileCacheKey, Tile> Tiles;
131
132 TileCache() :
133 _taskContext(1),
134 _cacheCleanupTimer(_taskContext, CACHE_CLEANUP_INTERVAL)
135 {
137
138 // init timer
139 OnCacheCleanupTimerTick();
140
141 // start the worker
142 _builderThread = std::thread([this] { _taskContext.run(); });
143 }
144
145 TileCache(TileCache const&) = delete;
146 TileCache(TileCache&&) = delete;
147
148 TileCache& operator=(TileCache const&) = delete;
149 TileCache& operator=(TileCache&&) = delete;
150
151 ~TileCache()
152 {
153 _cacheCleanupTimer.cancel();
154 _builderThread.join();
155 }
156
157 template <typename Task>
158 auto StartTask(Task&& task)
159 {
160 return Trinity::Asio::post(_taskContext, std::forward<Task>(task));
161 }
162
163private:
164 void OnCacheCleanupTimerTick()
165 {
166 TimePoint now = GameTime::Now();
167 RemoveOldCacheEntries(now - CACHE_MAX_AGE);
168 _cacheCleanupTimer.expires_at(now + CACHE_CLEANUP_INTERVAL);
169 _cacheCleanupTimer.async_wait([this](boost::system::error_code const& error)
170 {
171 if (error || !_builderThread.joinable() /*shutting down*/)
172 return;
173
174 OnCacheCleanupTimerTick();
175 });
176 }
177
178 void RemoveOldCacheEntries(TimePoint oldestPreservedEntryTimestamp)
179 {
180 std::scoped_lock lock(TilesMutex);
181 Trinity::Containers::EraseIf(Tiles, [=](std::unordered_map<TileCacheKey, Tile>::value_type const& kv)
182 {
183 return kv.second.LastAccessed < oldestPreservedEntryTimestamp;
184 });
185 }
186
187 Trinity::Asio::IoContext _taskContext;
188 Trinity::Asio::DeadlineTimer _cacheCleanupTimer;
189 std::thread _builderThread;
190};
191}
192
193namespace MMAP
194{
196{
200
201 friend bool operator==(TileId const&, TileId const&) = default;
202};
203
205{
207 std::weak_ptr<DynamicTileBuilder::AsyncTileResult> Result;
208 dtNavMesh* NavMesh;
209};
210
212{
213 std::shared_ptr<DynamicTileBuilder::AsyncTileResult> result = request.Result.lock();
214 if (!result)
215 return true; // expired, mark as complete and do nothing
216
217 if (!result->IsReady.load(std::memory_order::acquire))
218 return false;
219
220 TileBuilder::TileResult const& tileResult = result->Result;
221 if (tileResult.data)
222 {
223 dtMeshHeader const* header = reinterpret_cast<dtMeshHeader const*>(tileResult.data.get());
224
225 if (dtTileRef tileRef = request.NavMesh->getTileRefAt(header->x, header->y, 0))
226 {
227 TC_LOG_INFO("maps.mmapgen", "[Map {:04}] [{:02},{:02}]: Swapping new tile", request.Id.TerrainMapId, request.Id.Y, request.Id.X);
228
229 request.NavMesh->removeTile(tileRef, nullptr, nullptr);
230
231 unsigned char* data = static_cast<unsigned char*>(dtAlloc(tileResult.size, DT_ALLOC_PERM));
232 std::memcpy(data, tileResult.data.get(), tileResult.size);
233
234 request.NavMesh->addTile(data, tileResult.size, DT_TILE_FREE_DATA, tileRef, nullptr);
235 }
236 }
237
238 return true;
239}
240
242{
243 result->IsReady.store(true, std::memory_order::release);
244};
245
246DynamicTileBuilder::DynamicTileBuilder(Map* map, dtNavMesh* navMesh) : TileBuilder(sWorld->GetDataPath(), sWorld->GetDataPath(), {}, {}, false, false, false, nullptr),
247 m_map(map), m_navMesh(navMesh), m_rebuildCheckTimer(1s)
248{
249}
250
252
253void DynamicTileBuilder::AddTile(uint32 terrainMapId, uint32 tileX, uint32 tileY)
254{
255 TileId id = { .TerrainMapId = terrainMapId, .X = tileX, .Y = tileY };
257 m_tilesToRebuild.push_back(id);
258}
259
261{
264 {
265 for (TileId const& tileId : m_tilesToRebuild)
266 m_tiles.AddCallback({ .Id = tileId, .Result = BuildTile(tileId.TerrainMapId, tileId.X, tileId.Y), .NavMesh = m_navMesh });
267
268 m_tilesToRebuild.clear();
270 }
271
272 m_tiles.ProcessReadyCallbacks();
273}
274
275std::weak_ptr<DynamicTileBuilder::AsyncTileResult> DynamicTileBuilder::BuildTile(uint32 terrainMapId, uint32 tileX, uint32 tileY)
276{
277 struct GameObjectModelWorkData
278 {
279 explicit GameObjectModelWorkData(std::shared_ptr<VMAP::WorldModel const> worldModel, G3D::Vector3 const& position, float scale,
280 G3D::Quat const& rotation, GameObjectModel const* gameObject) : WorldModel(std::move(worldModel)), Position(position),
281 Rotation(rotation), Scale(scale), GameObject(gameObject) { }
282
283 std::shared_ptr<VMAP::WorldModel const> WorldModel;
284 G3D::Vector3 Position;
285 G3D::Quat Rotation;
286 float Scale;
288 };
289
290 std::vector<GameObjectModelWorkData> modelSpawns;
291 for (GameObjectModel const* gameObjectModel : m_map->GetGameObjectModelsInGrid(tileX, tileY))
292 {
293 if (!gameObjectModel->IsIncludedInNavMesh())
294 continue;
295
296 std::shared_ptr<VMAP::WorldModel const> worldModel = gameObjectModel->GetWorldModel();
297 if (!worldModel)
298 continue;
299
300 modelSpawns.emplace_back(std::move(worldModel), gameObjectModel->GetPosition(),
301 gameObjectModel->GetScale(), gameObjectModel->GetRotation(), gameObjectModel);
302 }
303
304 TileCacheKey cacheKey{ .TerrainMapId = terrainMapId, .X = tileX, .Y = tileY, .CachedHash = 0, .Objects = std::vector<TileCacheKeyObject>(modelSpawns.size()) };
305 for (std::size_t i = 0; i < modelSpawns.size(); ++i)
306 {
307 GameObjectModel const* gameObjectModel = modelSpawns[i].GameObject;
308 TileCacheKeyObject& object = cacheKey.Objects[i];
309 object.DisplayId = gameObjectModel->GetDisplayId();
310 object.Scale = int16(gameObjectModel->GetScale() * 1024.0f);
311 object.Position = [](G3D::Vector3 const& pos) -> std::array<int16, 3>
312 {
313 return { int16(pos.x), int16(pos.y), int16(pos.z) };
314 }(gameObjectModel->GetPosition());
315 object.Rotation = gameObjectModel->GetPackedRotation();
316 }
317
318 // Ensure spawn order is stable after adding/removing gameobjects from the map for hash calculation
319 std::ranges::sort(cacheKey.Objects);
320
321 cacheKey.CachedHash = std::hash<TileCacheKey>::Compute(cacheKey);
322
323 TileCache* tileCache = TileCache::Instance();
324 std::scoped_lock lock(tileCache->TilesMutex);
325 auto [itr, isNew] = tileCache->Tiles.try_emplace(std::move(cacheKey));
326 itr->second.LastAccessed = GameTime::Now();
327 if (!isNew)
328 return itr->second.Data;
329
330 itr->second.Data = std::make_shared<AsyncTileResult>();
331 tileCache->StartTask([result = itr->second.Data, hash = cacheKey.CachedHash, selfRef = weak_from_this(), terrainMapId, tileX, tileY, modelSpawns = std::move(modelSpawns)]() mutable
332 {
333 auto isReadyGuard = Trinity::make_unique_ptr_with_deleter<SetAsyncCallbackReady>(result.get());
334
335 std::shared_ptr<DynamicTileBuilder> self = selfRef.lock();
336 if (!self)
337 return;
338
339 // get navmesh params
340 dtNavMeshParams params;
341 std::vector<OffMeshData> offMeshConnections;
342 if (MMapManager::parseNavMeshParamsFile(sWorld->GetDataPath(), terrainMapId, &params, &offMeshConnections) != LoadResult::Success)
343 return;
344
345 std::unique_ptr<VMAP::VMapManager> vmapManager = CreateVMapManager(terrainMapId);
346
347 MeshData meshData;
348
349 // get heightmap data
350 self->m_terrainBuilder.loadMap(terrainMapId, tileX, tileY, meshData, vmapManager.get());
351
352 // get model data
353 self->m_terrainBuilder.loadVMap(terrainMapId, tileX, tileY, meshData, vmapManager.get());
354
355 for (GameObjectModelWorkData const& model : modelSpawns)
356 {
357 G3D::Vector3 position = model.Position;
358 position.x = -position.x;
359 position.y = -position.y;
360
361 G3D::Matrix3 invRotation = (G3D::Quat(0, 0, 1, 0) * model.Rotation).toRotationMatrix().inverse();
362
363 self->m_terrainBuilder.loadVMapModel(model.WorldModel.get(), position, invRotation, model.Scale,
364 meshData, vmapManager.get());
365 }
366
367 // if there is no data, give up now
368 if (meshData.solidVerts.empty() && meshData.liquidVerts.empty())
369 return;
370
371 // remove unused vertices
372 TerrainBuilder::cleanVertices(meshData.solidVerts, meshData.solidTris);
373 TerrainBuilder::cleanVertices(meshData.liquidVerts, meshData.liquidTris);
374
375 // gather all mesh data for final data check, and bounds calculation
376 std::vector<float> allVerts(meshData.liquidVerts.size() + meshData.solidVerts.size());
377 std::ranges::copy(meshData.liquidVerts, allVerts.begin());
378 std::ranges::copy(meshData.solidVerts, allVerts.begin() + std::ssize(meshData.liquidVerts));
379
380 // get bounds of current tile
381 float bmin[3], bmax[3];
382 getTileBounds(tileX, tileY, allVerts.data(), allVerts.size() / 3, bmin, bmax);
383
384 self->m_terrainBuilder.loadOffMeshConnections(terrainMapId, tileX, tileY, meshData, offMeshConnections);
385
386 // build navmesh tile
387 std::string debugSuffix = Trinity::StringFormat("_{:016X}", hash);
388
389 result->Result = self->buildMoveMapTile(terrainMapId, tileX, tileY, meshData, bmin, bmax, &params);
390 if (self->m_debugOutput && result->Result.data)
391 self->saveMoveMapTileToFile(terrainMapId, tileX, tileY, nullptr, result->Result, debugSuffix);
392 });
393
394 return itr->second.Data;
395}
396}
DB2Storage< MapEntry > sMapStore("Map.db2", &MapLoadInfo::Instance)
int64_t int64
Definition Define.h:149
int16_t int16
Definition Define.h:151
uint32_t uint32
Definition Define.h:154
std::unordered_set< uint32 > params[2]
std::chrono::milliseconds Milliseconds
Milliseconds shorthand typedef.
Definition Duration.h:24
std::chrono::steady_clock::time_point TimePoint
time_point shorthand typedefs
Definition Duration.h:40
#define TC_LOG_INFO(filterType__, message__,...)
Definition Log.h:184
std::strong_ordering operator<=>(WowTime const &left, WowTime const &right)
Definition WowTime.cpp:142
int64 GetPackedRotation() const
float GetScale() const
uint32 GetDisplayId() const
G3D::Vector3 const & GetPosition() const
void AddTile(uint32 terrainMapId, uint32 tileX, uint32 tileY)
std::vector< TileId > m_tilesToRebuild
AsyncCallbackProcessor< TileBuildRequest > m_tiles
std::weak_ptr< AsyncTileResult > BuildTile(uint32 terrainMapId, uint32 tileX, uint32 tileY)
DynamicTileBuilder(Map *map, dtNavMesh *navMesh)
Definition Map.h:225
static VMapManager * createOrGetVMapManager()
GetLiquidFlagsFn GetLiquidFlagsPtr
IsVMAPDisabledForFn IsVMAPDisabledForPtr
#define sWorld
Definition World.h:916
TimePoint Now()
Current chrono steady_clock time point.
Definition GameTime.cpp:67
bool InvokeAsyncCallbackIfReady(TileBuildRequest &request)
std::unique_ptr< VMAP::VMapManager >(* CreateVMapManager)(uint32 mapId)
static constexpr auto SetAsyncCallbackReady
decltype(auto) post(boost::asio::io_context &ioContext, T &&t)
Definition IoContext.h:54
constexpr void EraseIf(Container &c, Predicate p)
Definition Containers.h:283
bool operator==(unique_trackable_ptr< T1 > const &left, unique_trackable_ptr< T2 > const &right)
std::string StringFormat(FormatString< Args... > fmt, Args &&... args) noexcept
Default TC string format function.
struct advstd::ranges::Contains contains
friend bool operator==(TileId const &, TileId const &)=default
std::vector< float > liquidVerts
std::vector< float > solidVerts
std::vector< int > solidTris
std::vector< int > liquidTris
std::weak_ptr< DynamicTileBuilder::AsyncTileResult > Result
DynamicTileBuilder::TileId Id
int16 ParentMapID
void Update(int32 diff)
Definition Timer.h:121
bool Passed() const
Definition Timer.h:131
void Reset(int32 expiry)
Definition Timer.h:136
constexpr void UpdateData(std::span< V, Extent > data) noexcept
Definition Hash.h:85
std::size_t operator()(TileCacheKey const &key) const noexcept
static std::size_t Compute(TileCacheKey const &key) noexcept