TrinityCore
Loading...
Searching...
No Matches
MMapManager.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
18#include "MMapManager.h"
19#include "Errors.h"
20#include "Hash.h"
21#include "Log.h"
22#include "MMapDefines.h"
23#include "MapUtils.h"
24#include "Memory.h"
25#include <algorithm>
26
27namespace MMAP
28{
29 constexpr char MAP_FILE_NAME_FORMAT[] = "{}mmaps/{:04}.mmap";
30 constexpr char TILE_FILE_NAME_FORMAT[] = "{}mmaps/{:04}_{:02}_{:02}.mmtile";
31
32 using NavMeshQuerySet = std::unordered_map<std::pair<uint32, uint32>, dtNavMeshQuery>;
33 using MMapTileSet = std::unordered_map<uint32, dtTileRef>;
34
36 {
37 dtNavMesh navMesh;
38 MMapTileSet loadedTileRefs; // maps [map grid coords] to [dtTile]
39 };
40
41 using MeshDataMap = std::unordered_map<uint32, MMapMapData>;
42
43 // dummy struct to hold map's mmap data
44 struct MMapData
45 {
47
48 // we have to use single dtNavMeshQuery for every instance, since those are not thread safe
49 NavMeshQuerySet navMeshQueries; // instanceId to query
50
52 {
53 switch (mapId)
54 {
55 case 0: case 1: case 571: case 603: case 607: case 609: case 616: case 628: case 631: case 644: case 649: case 720:
56 case 732: case 754: case 755: case 861: case 938: case 940: case 962: case 967: case 1064: case 1076: case 1098:
57 case 1122: case 1126: case 1182: case 1205: case 1220: case 1265: case 1492: case 1523: case 1530: case 1579: case 1676:
58 case 1704: case 1705: case 1706: case 1707: case 1734: case 1756: case 1943: case 2076: case 2118: case 2160: case 2161:
59 case 2187: case 2212: case 2235: case 2237: case 2264: case 2450: case 2512: case 2586: case 2601: case 2654: case 2657:
60 case 2660: case 2669: case 2819: case 2828:
61 return instanceId;
62 default:
63 break;
64 }
65
66 // for maps that won't have dynamic mesh, return 0 to reuse the same mesh across all instances
67 return 0;
68 }
69
70 std::pair<MeshDataMap::iterator, bool> GetMeshData(uint32 mapId, uint32 instanceId)
71 {
72 return meshData.try_emplace(GetInstanceIdForMeshLookup(mapId, instanceId));
73 }
74
75 MeshDataMap::iterator FindMeshData(uint32 mapId, uint32 instanceId)
76 {
77 return meshData.find(GetInstanceIdForMeshLookup(mapId, instanceId));
78 }
79
80 MeshDataMap::node_type RemoveMeshData(uint32 mapId, uint32 instanceId)
81 {
82 return meshData.extract(GetInstanceIdForMeshLookup(mapId, instanceId));
83 }
84 };
85
86 // ######################## MMapManager ########################
87 MMapManager::MMapManager() = default;
88 MMapManager::~MMapManager() = default;
89
91 {
92 static MMapManager instance;
93 return &instance;
94 }
95
96 void MMapManager::InitializeThreadUnsafe(std::unordered_map<uint32, std::vector<uint32>> const& mapData)
97 {
98 // the caller must pass the list of all mapIds that will be used in the MMapManager lifetime
99 for (auto const& [mapId, childMapIds] : mapData)
100 {
101 loadedMMaps.try_emplace(mapId, new MMapData());
102 for (uint32 childMapId : childMapIds)
103 parentMapData[childMapId] = mapId;
104 }
105 }
106
107 MMapDataSet::const_iterator MMapManager::GetMMapData(uint32 mapId) const
108 {
109 // return the iterator if found or end() if not found/NULL
110 MMapDataSet::const_iterator itr = loadedMMaps.find(mapId);
111 if (itr != loadedMMaps.cend() && !itr->second)
112 itr = loadedMMaps.cend();
113
114 return itr;
115 }
116
121
122 LoadResult MMapManager::loadMapData(std::string_view basePath, uint32 mapId, uint32 instanceId)
123 {
124 // we already have this map loaded?
125 MMapDataSet::iterator itr = loadedMMaps.find(mapId);
126 ASSERT(itr != loadedMMaps.end(), "Invalid mapId %u passed to MMapManager after startup in thread unsafe environment", mapId);
127
128 auto [meshItr, needsLoading] = itr->second->GetMeshData(mapId, instanceId);
129 if (!needsLoading)
131
132 auto loadGuard = Trinity::make_unique_ptr_with_deleter(&meshItr, [&](MeshDataMap::iterator* m)
133 {
134 itr->second->meshData.erase(*m);
135 });
136
137 // load and init dtNavMesh - read parameters from file
138 dtNavMeshParams params;
139 if (LoadResult paramsResult = parseNavMeshParamsFile(basePath, mapId, &params); paramsResult != LoadResult::Success)
140 return paramsResult;
141
142 if (dtStatusFailed(meshItr->second.navMesh.init(&params)))
143 {
144 TC_LOG_ERROR("maps", "MMAP:loadMapData: Failed to initialize dtNavMesh for mmap {:04}", mapId);
146 }
147
148 TC_LOG_DEBUG("maps", "MMAP:loadMapData: Loaded {:04}.mmap", mapId);
149 (void)loadGuard.release();
150
151 return LoadResult::Success;
152 }
153
154 LoadResult MMapManager::parseNavMeshParamsFile(std::string_view basePath, uint32 mapId, dtNavMeshParams* params,
155 std::vector<OffMeshData>* offmeshConnections /*= nullptr*/)
156 {
157 std::string fileName = Trinity::StringFormat(MAP_FILE_NAME_FORMAT, basePath, mapId);
158 auto file = Trinity::make_unique_ptr_with_deleter<&::fclose>(fopen(fileName.c_str(), "rb"));
159 if (!file)
160 {
161 TC_LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not open mmap file '{}'", fileName);
163 }
164
165 MmapNavMeshHeader fileHeader;
166 if (fread(&fileHeader, sizeof(MmapNavMeshHeader), 1, file.get()) != 1)
167 {
168 TC_LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not read params from file '{}'", fileName);
170 }
171
172 if (fileHeader.mmapMagic != MMAP_MAGIC)
173 {
174 TC_LOG_ERROR("maps", "MMAP:loadMap: Bad header in mmap {:04}.mmap", mapId);
176 }
177
178 if (fileHeader.mmapVersion != MMAP_VERSION)
179 {
180 TC_LOG_ERROR("maps", "MMAP:loadMap: {:04}.mmap was built with generator v{}, expected v{}",
181 mapId, fileHeader.mmapVersion, MMAP_VERSION);
183 }
184
185 memcpy(params, &fileHeader.params, sizeof(dtNavMeshParams));
186
187 if (offmeshConnections)
188 {
189 offmeshConnections->resize(fileHeader.offmeshConnectionCount);
190 if (fread(offmeshConnections->data(), sizeof(OffMeshData), offmeshConnections->size(), file.get()) != offmeshConnections->size())
191 {
192 offmeshConnections->clear();
193 TC_LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not read offmesh connections from file '{}'", fileName);
195 }
196 }
197
198 return LoadResult::Success;
199 }
200
202 {
203 return uint32(x << 16 | y);
204 }
205
206 LoadResult MMapManager::loadMap(std::string_view basePath, uint32 mapId, uint32 instanceId, int32 x, int32 y)
207 {
208 // make sure the mmap is loaded and ready to load tiles
209 switch (LoadResult mapResult = loadMapData(basePath, mapId, instanceId))
210 {
213 break;
214 default:
215 return mapResult;
216 }
217
218 // get this mmap data
220 MMapMapData& meshData = mmap->GetMeshData(mapId, instanceId).first->second;
221
222 // check if we already have this tile loaded
223 uint32 packedGridPos = packTileID(x, y);
224 if (meshData.loadedTileRefs.contains(packedGridPos))
226
227 // load this tile :: mmaps/MMMM_XX_YY.mmtile
228 std::string fileName = Trinity::StringFormat(TILE_FILE_NAME_FORMAT, basePath, mapId, x, y);
229 auto file = Trinity::make_unique_ptr_with_deleter<&::fclose>(fopen(fileName.c_str(), "rb"));
230 if (!file)
231 {
232 auto parentMapItr = parentMapData.find(mapId);
233 if (parentMapItr != parentMapData.end())
234 {
235 fileName = Trinity::StringFormat(TILE_FILE_NAME_FORMAT, basePath, parentMapItr->second, x, y);
236 file.reset(fopen(fileName.c_str(), "rb"));
237 }
238 }
239
240 if (!file)
241 {
242 TC_LOG_DEBUG("maps", "MMAP:loadMap: Could not open mmtile file '{}'", fileName);
244 }
245
246 // read header
247 MmapTileHeader fileHeader;
248 if (fread(&fileHeader, sizeof(MmapTileHeader), 1, file.get()) != 1)
249 {
250 TC_LOG_ERROR("maps", "MMAP:loadMap: Bad header in mmap {:04}_{:02}_{:02}.mmtile", mapId, x, y);
252 }
253
254 if (fileHeader.mmapMagic != MMAP_MAGIC)
255 {
256 TC_LOG_ERROR("maps", "MMAP:loadMap: Bad header in mmap {:04}_{:02}_{:02}.mmtile", mapId, x, y);
258 }
259
260 if (fileHeader.mmapVersion != MMAP_VERSION)
261 {
262 TC_LOG_ERROR("maps", "MMAP:loadMap: {:04}_{:02}_{:02}.mmtile was built with generator v{}, expected v{}",
263 mapId, x, y, fileHeader.mmapVersion, MMAP_VERSION);
265 }
266
267 long pos = ftell(file.get());
268 fseek(file.get(), 0, SEEK_END);
269 if (pos < 0 || static_cast<int32>(fileHeader.size) > ftell(file.get()) - pos)
270 {
271 TC_LOG_ERROR("maps", "MMAP:loadMap: {:04}_{:02}_{:02}.mmtile has corrupted data size", mapId, x, y);
273 }
274
275 fseek(file.get(), pos, SEEK_SET);
276
277 auto data = Trinity::make_unique_ptr_with_deleter<&::dtFree>(dtAlloc(fileHeader.size, DT_ALLOC_PERM));
278 ASSERT(data);
279
280 size_t result = fread(data.get(), fileHeader.size, 1, file.get());
281 if (!result)
282 {
283 TC_LOG_ERROR("maps", "MMAP:loadMap: Bad header or data in mmap {:04}_{:02}_{:02}.mmtile", mapId, x, y);
285 }
286
287 dtMeshHeader* header = static_cast<dtMeshHeader*>(data.get());
288 dtTileRef tileRef = 0;
289
290 // memory allocated for data is now managed by detour, and will be deallocated when the tile is removed
291 if (dtStatusSucceed(meshData.navMesh.addTile(static_cast<unsigned char*>(data.release()), fileHeader.size, DT_TILE_FREE_DATA, 0, &tileRef)))
292 {
293 meshData.loadedTileRefs[packedGridPos] = tileRef;
294 ++loadedTiles;
295 TC_LOG_DEBUG("maps", "MMAP:loadMap: Loaded mmtile {:04}[{:02}, {:02}] into {:04}[{:02}, {:02}]", mapId, x, y, mapId, header->x, header->y);
296 return LoadResult::Success;
297 }
298 else
299 {
300 TC_LOG_ERROR("maps", "MMAP:loadMap: Could not load {:04}_{:02}_{:02}.mmtile into navmesh", mapId, x, y);
302 }
303 }
304
305 bool MMapManager::loadMapInstance(std::string_view basePath, uint32 meshMapId, uint32 instanceMapId, uint32 instanceId)
306 {
307 switch (loadMapData(basePath, meshMapId, instanceId))
308 {
311 break;
312 default:
313 return false;
314 }
315
317 auto [queryItr, inserted] = mmap->navMeshQueries.try_emplace({ instanceMapId, instanceId });
318 if (!inserted)
319 return true;
320
321 auto loadGuard = Trinity::make_unique_ptr_with_deleter(&queryItr, [&](NavMeshQuerySet::iterator* m)
322 {
323 mmap->navMeshQueries.erase(*m);
324 });
325
326 // allocate mesh query
327 if (dtStatusFailed(queryItr->second.init(&mmap->GetMeshData(meshMapId, instanceId).first->second.navMesh, 1024)))
328 {
329 TC_LOG_ERROR("maps", "MMAP:GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId {:04} instanceId {}", instanceMapId, instanceId);
330 return false;
331 }
332
333 TC_LOG_DEBUG("maps", "MMAP:GetNavMeshQuery: created dtNavMeshQuery for mapId {:04} instanceId {}", instanceMapId, instanceId);
334 (void)loadGuard.release();
335 return true;
336 }
337
339 {
340 // check if we have this map loaded
341 MMapDataSet::const_iterator itr = GetMMapData(mapId);
342 if (itr == loadedMMaps.end())
343 {
344 // file may not exist, therefore not loaded
345 TC_LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh map. {:04}_{:02}_{:02}.mmtile", mapId, x, y);
346 return;
347 }
348
349 MMapData* mmap = itr->second.get();
350 uint32 packedGridPos = packTileID(x, y);
351 for (auto& [instanceId, meshData] : mmap->meshData)
352 {
353 // check if we have this tile loaded
354 auto tileRef = meshData.loadedTileRefs.extract(packedGridPos);
355 if (!tileRef)
356 continue;
357
358 // unload, and mark as non loaded
359 if (dtStatusFailed(meshData.navMesh.removeTile(tileRef.mapped(), nullptr, nullptr)))
360 {
361 // this is technically a memory leak
362 // if the grid is later reloaded, dtNavMesh::addTile will return error but no extra memory is used
363 // we cannot recover from this error - assert out
364 TC_LOG_ERROR("maps", "MMAP:unloadMap: Could not unload {:04}_{:02}_{:02}.mmtile from navmesh", mapId, x, y);
365 ABORT();
366 }
367 else
368 {
369 --loadedTiles;
370 TC_LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile {:04}[{:02}, {:02}] from {:03}", mapId, x, y, mapId);
371 }
372 }
373 }
374
376 {
377 MMapDataSet::iterator itr = loadedMMaps.find(mapId);
378 if (itr == loadedMMaps.end() || !itr->second)
379 {
380 // file may not exist, therefore not loaded
381 TC_LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh map {:04}", mapId);
382 return;
383 }
384
386 {
387 if (MeshDataMap::node_type meshNode = itr->second->RemoveMeshData(mapId, 0))
388 {
389 for (auto const& [tileId, tileRef] : meshNode.mapped().loadedTileRefs)
390 {
391 uint32 x = (tileId >> 16);
392 uint32 y = (tileId & 0x0000FFFF);
393 if (dtStatusFailed(meshNode.mapped().navMesh.removeTile(tileRef, nullptr, nullptr)))
394 TC_LOG_ERROR("maps", "MMAP:unloadMap: Could not unload {:04}_{:02}_{:02}.mmtile from navmesh", mapId, x, y);
395 else
396 {
397 --loadedTiles;
398 TC_LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile {:04}[{:02}, {:02}] from {:04}", mapId, x, y, mapId);
399 }
400 }
401 }
402 }
403 else // require all tiles to be already unloaded
404 ASSERT(std::ranges::all_of(itr->second->meshData, [](MMapMapData const& mesh) { return mesh.loadedTileRefs.empty(); }, Trinity::Containers::MapValue));
405
406 TC_LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded {:04}.mmap", mapId);
407 }
408
409 void MMapManager::unloadMapInstance(uint32 meshMapId, uint32 instanceMapId, uint32 instanceId)
410 {
411 // check if we have this map loaded
412 MMapDataSet::const_iterator itr = GetMMapData(meshMapId);
413 if (itr == loadedMMaps.end())
414 {
415 // file may not exist, therefore not loaded
416 TC_LOG_DEBUG("maps", "MMAP:unloadMapInstance: Asked to unload not loaded navmesh map {:04}", meshMapId);
417 return;
418 }
419
420 MMapData* mmap = itr->second.get();
421 std::size_t erased = mmap->navMeshQueries.erase({ instanceMapId, instanceId });
422 if (!erased)
423 TC_LOG_DEBUG("maps", "MMAP:unloadMapInstance: Asked to unload not loaded dtNavMeshQuery mapId {:04} instanceId {}", instanceMapId, instanceId);
424
425 if (isRebuildingTilesEnabledOnMap(meshMapId))
426 {
427 if (MeshDataMap::node_type meshNode = mmap->RemoveMeshData(meshMapId, instanceId))
428 {
429 // unload all tiles from given map
430 for (auto const& [tileId, tileRef] : meshNode.mapped().loadedTileRefs)
431 {
432 uint32 x = (tileId >> 16);
433 uint32 y = (tileId & 0x0000FFFF);
434 if (dtStatusFailed(meshNode.mapped().navMesh.removeTile(tileRef, nullptr, nullptr)))
435 TC_LOG_ERROR("maps", "MMAP:unloadMap: Could not unload {:04}_{:02}_{:02}.mmtile from navmesh", meshMapId, x, y);
436 else
437 {
438 --loadedTiles;
439 TC_LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile {:04}[{:02}, {:02}] from {:04}", meshMapId, x, y, meshMapId);
440 }
441 }
442 }
443 }
444
445 TC_LOG_DEBUG("maps", "MMAP:unloadMapInstance: Unloaded mapId {:04} instanceId {}", instanceMapId, instanceId);
446 }
447
448 dtNavMesh* MMapManager::GetNavMesh(uint32 mapId, uint32 instanceId)
449 {
450 MMapDataSet::const_iterator itr = GetMMapData(mapId);
451 if (itr == loadedMMaps.end())
452 return nullptr;
453
454 MeshDataMap::iterator meshItr = itr->second->FindMeshData(mapId, instanceId);
455 if (meshItr == itr->second->meshData.end())
456 return nullptr;
457
458 return &meshItr->second.navMesh;
459 }
460
461 dtNavMeshQuery const* MMapManager::GetNavMeshQuery(uint32 meshMapId, uint32 instanceMapId, uint32 instanceId)
462 {
463 MMapDataSet::const_iterator itr = GetMMapData(meshMapId);
464 if (itr == loadedMMaps.end())
465 return nullptr;
466
467 auto queryItr = itr->second->navMeshQueries.find({ instanceMapId, instanceId });
468 if (queryItr == itr->second->navMeshQueries.end())
469 return nullptr;
470
471 return &queryItr->second;
472 }
473}
int32_t int32
Definition Define.h:150
uint32_t uint32
Definition Define.h:154
std::unordered_set< uint32 > params[2]
#define ABORT
Definition Errors.h:87
#define ASSERT
Definition Errors.h:80
#define TC_LOG_DEBUG(filterType__, message__,...)
Definition Log.h:181
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
uint32 constexpr MMAP_VERSION
Definition MMapDefines.h:25
uint32 constexpr MMAP_MAGIC
Definition MMapDefines.h:24
static bool isRebuildingTilesEnabledOnMap(uint32 mapId)
static MMapManager * instance()
dtNavMesh * GetNavMesh(uint32 mapId, uint32 instanceId)
bool loadMapInstance(std::string_view basePath, uint32 meshMapId, uint32 instanceMapId, uint32 instanceId)
LoadResult loadMap(std::string_view basePath, uint32 mapId, uint32 instanceId, int32 x, int32 y)
std::unordered_map< uint32, uint32 > parentMapData
Definition MMapManager.h:85
void unloadMap(uint32 mapId, int32 x, int32 y)
void unloadMapInstance(uint32 meshMapId, uint32 instanceMapId, uint32 instanceId)
MMapDataSet loadedMMaps
Definition MMapManager.h:82
MMapDataSet::const_iterator GetMMapData(uint32 mapId) const
void InitializeThreadUnsafe(std::unordered_map< uint32, std::vector< uint32 > > const &mapData)
LoadResult loadMapData(std::string_view basePath, uint32 mapId, uint32 instanceId)
dtNavMeshQuery const * GetNavMeshQuery(uint32 meshMapId, uint32 instanceMapId, uint32 instanceId)
uint32 packTileID(int32 x, int32 y)
static LoadResult parseNavMeshParamsFile(std::string_view basePath, uint32 mapId, dtNavMeshParams *params, std::vector< OffMeshData > *offmeshConnections=nullptr)
std::unordered_map< uint32, MMapMapData > MeshDataMap
std::unordered_map< uint32, dtTileRef > MMapTileSet
constexpr char TILE_FILE_NAME_FORMAT[]
std::unordered_map< std::pair< uint32, uint32 >, dtNavMeshQuery > NavMeshQuerySet
constexpr char MAP_FILE_NAME_FORMAT[]
constexpr auto MapValue
Definition MapUtils.h:77
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition MapUtils.h:37
std::unique_ptr< T, Impl::stateful_unique_ptr_deleter< Ptr, Del > > make_unique_ptr_with_deleter(Ptr ptr, Del deleter)
Definition Memory.h:133
std::string StringFormat(FormatString< Args... > fmt, Args &&... args) noexcept
Default TC string format function.
static uint32 GetInstanceIdForMeshLookup(uint32 mapId, uint32 instanceId)
MeshDataMap::node_type RemoveMeshData(uint32 mapId, uint32 instanceId)
MeshDataMap::iterator FindMeshData(uint32 mapId, uint32 instanceId)
NavMeshQuerySet navMeshQueries
std::pair< MeshDataMap::iterator, bool > GetMeshData(uint32 mapId, uint32 instanceId)
MeshDataMap meshData
MMapTileSet loadedTileRefs
dtNavMeshParams params
Definition MMapDefines.h:31
uint32 offmeshConnectionCount
Definition MMapDefines.h:32
uint32 mmapVersion
Definition MMapDefines.h:41