From 033a640aa542f8d0b38bc35e861c4b8b4c1a6310 Mon Sep 17 00:00:00 2001 From: Hondacrx Date: Sat, 31 Aug 2024 18:34:42 -0400 Subject: [PATCH] Core/Vmaps: Optimize loading vmaps Port From (https://github.com/TrinityCore/TrinityCore/commit/35a2d49971984bcab96ac64fc52b30a9a4e06ce3) --- .../Game/Collision/Management/VMapManager.cs | 60 ++++---- Source/Game/Collision/Maps/MapTree.cs | 137 +++++++----------- .../Game/Collision/Models/GameObjectModel.cs | 1 - Source/Game/Collision/Models/ModelInstance.cs | 6 + Source/Game/Collision/Models/WorldModel.cs | 4 + 5 files changed, 91 insertions(+), 117 deletions(-) diff --git a/Source/Game/Collision/Management/VMapManager.cs b/Source/Game/Collision/Management/VMapManager.cs index 18f7a462a..dc65fd7d1 100644 --- a/Source/Game/Collision/Management/VMapManager.cs +++ b/Source/Game/Collision/Management/VMapManager.cs @@ -77,7 +77,7 @@ namespace Game.Collision var instanceTree = iInstanceMapTrees.LookupByKey(mapId); if (instanceTree != null) { - instanceTree.UnloadMap(this); + instanceTree.UnloadMap(); if (instanceTree.NumLoadedTiles() == 0) { iInstanceMapTrees.Remove(mapId); @@ -186,22 +186,24 @@ namespace Game.Collision lock (LoadedModelFilesLock) { filename = filename.TrimEnd('\0'); - var model = iLoadedModelFiles.LookupByKey(filename); - if (model == null) + if (iLoadedModelFiles.TryGetValue(filename, out ManagedModel worldmodel)) + return worldmodel.Model; + + var model = new ManagedModel(); + if (!model.Model.ReadFile(VMapPath + filename)) { - model = new ManagedModel(); - if (!model.GetModel().ReadFile(VMapPath + filename)) - { - Log.outError(LogFilter.Server, "VMapManager: could not load '{0}'", filename); - return null; - } - - Log.outDebug(LogFilter.Maps, "VMapManager: loading file '{0}'", filename); - - iLoadedModelFiles.Add(filename, model); + Log.outError(LogFilter.Server, $"VMapManager: could not load '{filename}'"); + return null; } - model.IncRefCount(); - return model.GetModel(); + + Log.outDebug(LogFilter.Maps, $"VMapManager: loading file '{filename}'"); + + worldmodel.Model.SetName(filename); + + model = worldmodel; + + iLoadedModelFiles.Add(filename, model); + return model.Model; } } @@ -210,17 +212,14 @@ namespace Game.Collision lock (LoadedModelFilesLock) { filename = filename.TrimEnd('\0'); - var model = iLoadedModelFiles.LookupByKey(filename); - if (model == null) + var erased = iLoadedModelFiles.Remove(filename); + if (!erased) { - Log.outError(LogFilter.Server, "VMapManager: trying to unload non-loaded file '{0}'", filename); + Log.outError(LogFilter.Server, $"VMapManager: trying to unload non-loaded file '{filename}'"); return; } - if (model.DecRefCount() == 0) - { - Log.outDebug(LogFilter.Maps, "VMapManager: unloading file '{0}'", filename); - iLoadedModelFiles.Remove(filename); - } + + Log.outDebug(LogFilter.Maps, $"VMapManager: unloading file '{filename}'"); } } @@ -271,19 +270,12 @@ namespace Game.Collision public class ManagedModel { - public ManagedModel() + public WorldModel Model; + + ~ManagedModel() { - iModel = new(); - iRefCount = 0; + Global.VMapMgr.ReleaseModelInstance(Model.GetName()); } - - public void SetModel(WorldModel model) { iModel = model; } - public WorldModel GetModel() { return iModel; } - public void IncRefCount() { ++iRefCount; } - public int DecRefCount() { return --iRefCount; } - - WorldModel iModel; - int iRefCount; } public class AreaAndLiquidData diff --git a/Source/Game/Collision/Maps/MapTree.cs b/Source/Game/Collision/Maps/MapTree.cs index 82b97af4c..a41f5c157 100644 --- a/Source/Game/Collision/Maps/MapTree.cs +++ b/Source/Game/Collision/Maps/MapTree.cs @@ -53,7 +53,7 @@ namespace Game.Collision public LoadResult InitMap(string fname) { - Log.outDebug(LogFilter.Maps, "StaticMapTree.InitMap() : initializing StaticMapTree '{0}'", fname); + Log.outDebug(LogFilter.Maps, $"StaticMapTree.InitMap() : initializing StaticMapTree '{fname}'"); if (!File.Exists(fname)) return LoadResult.FileNotFound; @@ -64,45 +64,24 @@ namespace Game.Collision return LoadResult.VersionMismatch; var node = reader.ReadStringFromChars(4); - if (node != "NODE") + if (node != "NODE" || !iTree.ReadFromFile(reader)) return LoadResult.ReadFromFileFailed; - - if (!iTree.ReadFromFile(reader)) - return LoadResult.ReadFromFileFailed; - - iNTreeValues = iTree.PrimCount(); - iTreeValues = new ModelInstance[iNTreeValues]; - - if (reader.ReadStringFromChars(4) != "SIDX") - return LoadResult.ReadFromFileFailed; - - uint spawnIndicesSize = reader.ReadUInt32(); - for (uint i = 0; i < spawnIndicesSize; ++i) - { - uint spawnId = reader.ReadUInt32(); - iSpawnIndices[spawnId] = i; - } } + iTreeValues = new ModelInstance[iTree.PrimCount()]; + return LoadResult.Success; } - public void UnloadMap(VMapManager vm) + public void UnloadMap() { - foreach (var id in iLoadedSpawns) - { - for (uint refCount = 0; refCount < id.Key; ++refCount) - vm.ReleaseModelInstance(iTreeValues[id.Key].name); - - iTreeValues[id.Key].SetUnloaded(); - } - iLoadedSpawns.Clear(); + iTreeValues.Clear(); iLoadedTiles.Clear(); } public LoadResult LoadMapTile(int tileX, int tileY, VMapManager vm) { - if (iTreeValues == null) + if (iTreeValues.Empty()) { Log.outError(LogFilter.Server, "StaticMapTree.LoadMapTile() : tree has not been initialized [{0}, {1}]", tileX, tileY); return LoadResult.ReadFromFileFailed; @@ -111,16 +90,24 @@ namespace Game.Collision LoadResult result = LoadResult.FileNotFound; TileFileOpenResult fileResult = OpenMapTileFile(VMapManager.VMapPath, iMapID, tileX, tileY, vm); - if (fileResult.File != null) + if (fileResult.TileFile != null) { result = LoadResult.Success; - using BinaryReader reader = new(fileResult.File); + using BinaryReader reader = new(fileResult.TileFile); + using BinaryReader spawnIndicesReader = new(fileResult.SpawnIndicesFile); if (reader.ReadStringFromChars(8) != MapConst.VMapMagic) result = LoadResult.VersionMismatch; + if (spawnIndicesReader.ReadStringFromChars(8) != MapConst.VMapMagic) + result = LoadResult.VersionMismatch; if (result == LoadResult.Success) { uint numSpawns = reader.ReadUInt32(); + uint numSpawnIndices = spawnIndicesReader.ReadUInt32(); + + if (numSpawns != numSpawnIndices) + result = LoadResult.ReadFromFileFailed; + for (uint i = 0; i < numSpawns && result == LoadResult.Success; ++i) { // read model spawns @@ -132,31 +119,17 @@ namespace Game.Collision Log.outError(LogFilter.Server, "StaticMapTree.LoadMapTile() : could not acquire WorldModel [{0}, {1}]", tileX, tileY); // update tree - if (iSpawnIndices.ContainsKey(spawn.Id)) + int referencedVal = spawnIndicesReader.ReadInt32(); + if (referencedVal >= iTreeValues.Length) { - uint referencedVal = iSpawnIndices[spawn.Id]; - if (!iLoadedSpawns.ContainsKey(referencedVal)) - { - if (referencedVal >= iNTreeValues) - { - Log.outError(LogFilter.Maps, "StaticMapTree.LoadMapTile() : invalid tree element ({0}/{1}) referenced in tile {2}", referencedVal, iNTreeValues, fileResult.Name); - continue; - } + Log.outError(LogFilter.Maps, $"StaticMapTree::LoadMapTile() : invalid tree element ({referencedVal}/{iTreeValues.Length}) referenced in tile {fileResult.Name}"); + continue; + } - iTreeValues[referencedVal] = new ModelInstance(spawn, model); - iLoadedSpawns[referencedVal] = 1; - } - else - ++iLoadedSpawns[referencedVal]; - } - else if (iMapID == fileResult.UsedMapId) - { - // unknown parent spawn might appear in because it overlaps multiple tiles - // in case the original tile is swapped but its neighbour is now (adding this spawn) - // we want to not mark it as loading error and just skip that model - Log.outError(LogFilter.Maps, $"StaticMapTree.LoadMapTile() : invalid tree element (spawn {spawn.Id}) referenced in tile fileResult.Name{fileResult.Name} by map {iMapID}"); - result = LoadResult.ReadFromFileFailed; - } + if (iTreeValues[referencedVal].GetWorldModel() == null) + iTreeValues[referencedVal] = new ModelInstance(spawn, model); + + iTreeValues[referencedVal].AddTileReference(); } else { @@ -186,14 +159,19 @@ namespace Game.Collision if (iLoadedTiles[tileID]) // file associated with tile { TileFileOpenResult fileResult = OpenMapTileFile(VMapManager.VMapPath, iMapID, tileX, tileY, vm); - if (fileResult.File != null) + if (fileResult.TileFile != null) { - using BinaryReader reader = new(fileResult.File); + using BinaryReader reader = new(fileResult.TileFile); + using BinaryReader spawnIndicesReader = new(fileResult.SpawnIndicesFile); bool result = true; if (reader.ReadStringFromChars(8) != MapConst.VMapMagic) result = false; uint numSpawns = reader.ReadUInt32(); + uint numSpawnIndices = spawnIndicesReader.ReadUInt32(); + if (numSpawns != numSpawnIndices) + result = false; + for (uint i = 0; i < numSpawns && result; ++i) { // read model spawns @@ -201,52 +179,49 @@ namespace Game.Collision result = ModelSpawn.ReadFromFile(reader, out spawn); if (result) { - // release model instance - vm.ReleaseModelInstance(spawn.name); - // update tree - if (iSpawnIndices.ContainsKey(spawn.Id)) + int referencedNode = spawnIndicesReader.ReadInt32(); + if (referencedNode >= iTreeValues.Length) { - uint referencedNode = iSpawnIndices[spawn.Id]; - if (!iLoadedSpawns.ContainsKey(referencedNode)) - Log.outError(LogFilter.Server, "StaticMapTree.UnloadMapTile() : trying to unload non-referenced model '{0}' (ID:{1})", spawn.name, spawn.Id); - else if (--iLoadedSpawns[referencedNode] == 0) - { - iTreeValues[referencedNode].SetUnloaded(); - iLoadedSpawns.Remove(referencedNode); - } + Log.outError(LogFilter.Maps, $"StaticMapTree::LoadMapTile() : invalid tree element ({referencedNode}/{iTreeValues.Length}) referenced in tile {fileResult.Name}"); + continue; } - else if (iMapID == fileResult.UsedMapId) // logic documented in StaticMapTree::LoadMapTile - result = false; + + if (iTreeValues[referencedNode].GetWorldModel() == null) + Log.outError(LogFilter.Misc, $"StaticMapTree::UnloadMapTile() : trying to unload non-referenced model '{spawn.name}' (ID:{spawn.Id})"); + else if (iTreeValues[referencedNode].RemoveTileReference() == 0) + iTreeValues[referencedNode].SetUnloaded(); } } } } iLoadedTiles.Remove(tileID); } - + static uint PackTileID(int tileX, int tileY) { return (uint)(tileX << 16 | tileY); } static void UnpackTileID(int ID, ref int tileX, ref int tileY) { tileX = ID >> 16; tileY = ID & 0xFF; } static TileFileOpenResult OpenMapTileFile(string vmapPath, uint mapID, int tileX, int tileY, VMapManager vm) { TileFileOpenResult result = new(); - result.Name = vmapPath + GetTileFileName(mapID, tileX, tileY); + result.Name = vmapPath + GetTileFileName(mapID, tileX, tileY, "vmtile"); if (File.Exists(result.Name)) { result.UsedMapId = mapID; - result.File = new FileStream(result.Name, FileMode.Open, FileAccess.Read); + result.TileFile = new FileStream(result.Name, FileMode.Open, FileAccess.Read); + if (File.Exists(vmapPath + GetTileFileName(mapID, tileX, tileY, "vmtileidx"))) + result.SpawnIndicesFile = new FileStream(vmapPath + GetTileFileName(mapID, tileX, tileY, "vmtileidx"), FileMode.Open, FileAccess.Read); return result; } int parentMapId = vm.GetParentMapId(mapID); while (parentMapId != -1) { - result.Name = vmapPath + GetTileFileName((uint)parentMapId, tileX, tileY); + result.Name = vmapPath + GetTileFileName((uint)parentMapId, tileX, tileY, "vmtile"); if (File.Exists(result.Name)) { - result.File = new FileStream(result.Name, FileMode.Open, FileAccess.Read); + result.TileFile = new FileStream(result.Name, FileMode.Open, FileAccess.Read); result.UsedMapId = (uint)parentMapId; return result; } @@ -269,7 +244,7 @@ namespace Game.Collision return LoadResult.VersionMismatch; } - FileStream stream = OpenMapTileFile(vmapPath, mapID, tileX, tileY, vm).File; + FileStream stream = OpenMapTileFile(vmapPath, mapID, tileX, tileY, vm).TileFile; if (stream == null) return LoadResult.FileNotFound; @@ -282,9 +257,9 @@ namespace Game.Collision return LoadResult.Success; } - public static string GetTileFileName(uint mapID, int tileX, int tileY) + public static string GetTileFileName(uint mapID, int tileX, int tileY, string extension) { - return $"{mapID:D4}_{tileY:D2}_{tileX:D2}.vmtile"; + return $"{mapID:D4}_{tileY:D2}_{tileX:D2}.{extension}"; } public bool GetLocationInfo(Vector3 pos, LocationInfo info) @@ -383,18 +358,16 @@ namespace Game.Collision uint iMapID; BIH iTree = new(); - ModelInstance[] iTreeValues; - uint iNTreeValues; - Dictionary iSpawnIndices = new(); + ModelInstance[] iTreeValues; // the tree entries Dictionary iLoadedTiles = new(); - Dictionary iLoadedSpawns = new(); } class TileFileOpenResult { public string Name; - public FileStream File; + public FileStream TileFile; + public FileStream SpawnIndicesFile; public uint UsedMapId; } } diff --git a/Source/Game/Collision/Models/GameObjectModel.cs b/Source/Game/Collision/Models/GameObjectModel.cs index 7eb349555..6889f043d 100644 --- a/Source/Game/Collision/Models/GameObjectModel.cs +++ b/Source/Game/Collision/Models/GameObjectModel.cs @@ -78,7 +78,6 @@ namespace Game.Collision return !iModel.IsM2(); } - public override bool IntersectRay(Ray ray, ref float maxDist, bool stopAtFirstHit, PhaseShift phaseShift, ModelIgnoreFlags ignoreFlags) { if (!IsCollisionEnabled() || !owner.IsSpawned()) diff --git a/Source/Game/Collision/Models/ModelInstance.cs b/Source/Game/Collision/Models/ModelInstance.cs index 0cb90adb6..51da8ceac 100644 --- a/Source/Game/Collision/Models/ModelInstance.cs +++ b/Source/Game/Collision/Models/ModelInstance.cs @@ -74,6 +74,7 @@ namespace Game.Collision Matrix4x4 iInvRot; float iInvScale; WorldModel iModel; + uint referencingTiles; public ModelInstance() { @@ -174,5 +175,10 @@ namespace Game.Collision } public void SetUnloaded() { iModel = null; } + + public WorldModel GetWorldModel() { return iModel; } + + public void AddTileReference() { ++referencingTiles; } + public uint RemoveTileReference() { return --referencingTiles; } } } diff --git a/Source/Game/Collision/Models/WorldModel.cs b/Source/Game/Collision/Models/WorldModel.cs index 426ec13a9..8cd88264b 100644 --- a/Source/Game/Collision/Models/WorldModel.cs +++ b/Source/Game/Collision/Models/WorldModel.cs @@ -331,6 +331,7 @@ namespace Game.Collision uint RootWMOID; List groupModels = new(); BIH groupTree = new(); + string name; // valid only while model is held in VMapManager2::iLoadedModelFiles public override bool IntersectRay(Ray ray, ref float distance, bool stopAtFirstHit, ModelIgnoreFlags ignoreFlags) { @@ -422,6 +423,9 @@ namespace Game.Collision return groupTree.ReadFromFile(reader); } + public string GetName() { return name; } + public void SetName(string newName) { name = newName; } + public bool IsM2() { return Flags.HasFlag(ModelFlags.IsM2); } }