Core/Vmaps: Optimize loading vmaps
Port From (https://github.com/TrinityCore/TrinityCore/commit/35a2d49971984bcab96ac64fc52b30a9a4e06ce3)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<uint, uint> iSpawnIndices = new();
|
||||
ModelInstance[] iTreeValues; // the tree entries
|
||||
|
||||
Dictionary<uint, bool> iLoadedTiles = new();
|
||||
Dictionary<uint, uint> iLoadedSpawns = new();
|
||||
}
|
||||
|
||||
class TileFileOpenResult
|
||||
{
|
||||
public string Name;
|
||||
public FileStream File;
|
||||
public FileStream TileFile;
|
||||
public FileStream SpawnIndicesFile;
|
||||
public uint UsedMapId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +331,7 @@ namespace Game.Collision
|
||||
uint RootWMOID;
|
||||
List<GroupModel> 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); }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user