mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-19 19:53:53 +00:00
Limit NavMeshManager update range by player tile and max tiles
Object AABB may be much larger than area currently covered by navmesh. In this case all tiles beyond covered range should be ignored. Attempt to iterate over them will not result in any new tile updates but can take quite a while. At maximum this can be pow(INT_MAX - INT_MIN, 2) iterations. Use arbitrary time limit to check for update call to finish in the test.
This commit is contained in:
parent
ab54bf0641
commit
44429f0393
5 changed files with 99 additions and 55 deletions
|
@ -20,9 +20,12 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <condition_variable>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
MATCHER_P3(Vec3fEq, x, y, z, "")
|
MATCHER_P3(Vec3fEq, x, y, z, "")
|
||||||
{
|
{
|
||||||
|
@ -1226,4 +1229,33 @@ namespace
|
||||||
findPath(*mNavigator, agentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
findPath(*mNavigator, agentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||||
Status::NavMeshNotFound);
|
Status::NavMeshNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DetourNavigatorNavigatorTest, update_for_very_big_object_should_be_limited)
|
||||||
|
{
|
||||||
|
CollisionShapeInstance bigBox(std::make_unique<btBoxShape>(btVector3(1e9, 1e9, 1e9)));
|
||||||
|
|
||||||
|
mNavigator->updateBounds(mPlayerPosition, nullptr);
|
||||||
|
mNavigator->addAgent(mAgentBounds);
|
||||||
|
mNavigator->addObject(ObjectId(&bigBox.shape()), ObjectShapes(bigBox.instance(), mObjectTransform),
|
||||||
|
btTransform::getIdentity(), nullptr);
|
||||||
|
|
||||||
|
bool updated = false;
|
||||||
|
std::condition_variable updateFinished;
|
||||||
|
std::mutex mutex;
|
||||||
|
|
||||||
|
std::thread thread([&] {
|
||||||
|
mNavigator->update(mPlayerPosition, nullptr);
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
updated = true;
|
||||||
|
updateFinished.notify_all();
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock lock(mutex);
|
||||||
|
updateFinished.wait_for(lock, std::chrono::seconds(3), [&] { return updated; });
|
||||||
|
ASSERT_TRUE(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
|
|
||||||
|
#include <components/detournavigator/debug.hpp>
|
||||||
#include <components/detournavigator/settingsutils.hpp>
|
#include <components/detournavigator/settingsutils.hpp>
|
||||||
#include <components/detournavigator/tilecachedrecastmeshmanager.hpp>
|
#include <components/detournavigator/tilecachedrecastmeshmanager.hpp>
|
||||||
#include <components/esm/refid.hpp>
|
#include <components/esm/refid.hpp>
|
||||||
|
@ -82,10 +83,11 @@ namespace
|
||||||
TileCachedRecastMeshManager manager(mSettings);
|
TileCachedRecastMeshManager manager(mSettings);
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
||||||
TileBounds bounds;
|
const TilesPositionsRange range{
|
||||||
bounds.mMin = osg::Vec2f(182, 182);
|
.mBegin = TilePosition(0, 0),
|
||||||
bounds.mMax = osg::Vec2f(1000, 1000);
|
.mEnd = TilePosition(1, 1),
|
||||||
manager.setBounds(bounds, nullptr);
|
};
|
||||||
|
manager.setRange(range, nullptr);
|
||||||
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
|
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
|
||||||
EXPECT_THAT(manager.takeChangedTiles(nullptr), ElementsAre(std::pair(TilePosition(0, 0), ChangeType::add)));
|
EXPECT_THAT(manager.takeChangedTiles(nullptr), ElementsAre(std::pair(TilePosition(0, 0), ChangeType::add)));
|
||||||
}
|
}
|
||||||
|
@ -97,10 +99,11 @@ namespace
|
||||||
const btTransform transform(
|
const btTransform transform(
|
||||||
btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
|
btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
||||||
TileBounds bounds;
|
const TilesPositionsRange range{
|
||||||
bounds.mMin = osg::Vec2f(-1000, -1000);
|
.mBegin = TilePosition(-1, -1),
|
||||||
bounds.mMax = osg::Vec2f(1000, 1000);
|
.mEnd = TilePosition(2, 2),
|
||||||
manager.setBounds(bounds, nullptr);
|
};
|
||||||
|
manager.setRange(range, nullptr);
|
||||||
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, nullptr);
|
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, nullptr);
|
||||||
manager.takeChangedTiles(nullptr);
|
manager.takeChangedTiles(nullptr);
|
||||||
EXPECT_TRUE(
|
EXPECT_TRUE(
|
||||||
|
@ -130,10 +133,11 @@ namespace
|
||||||
TileCachedRecastMeshManager manager(mSettings);
|
TileCachedRecastMeshManager manager(mSettings);
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
||||||
TileBounds bounds;
|
const TilesPositionsRange range{
|
||||||
bounds.mMin = osg::Vec2f(182, 182);
|
.mBegin = TilePosition(0, 0),
|
||||||
bounds.mMax = osg::Vec2f(1000, 1000);
|
.mEnd = TilePosition(1, 1),
|
||||||
manager.setBounds(bounds, nullptr);
|
};
|
||||||
|
manager.setRange(range, nullptr);
|
||||||
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
|
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
|
||||||
manager.takeChangedTiles(nullptr);
|
manager.takeChangedTiles(nullptr);
|
||||||
manager.removeObject(ObjectId(&boxShape), nullptr);
|
manager.removeObject(ObjectId(&boxShape), nullptr);
|
||||||
|
@ -169,10 +173,11 @@ namespace
|
||||||
get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile)
|
get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile)
|
||||||
{
|
{
|
||||||
TileCachedRecastMeshManager manager(mSettings);
|
TileCachedRecastMeshManager manager(mSettings);
|
||||||
TileBounds bounds;
|
const TilesPositionsRange range{
|
||||||
bounds.mMin = osg::Vec2f(-1000, -1000);
|
.mBegin = TilePosition(-1, -1),
|
||||||
bounds.mMax = osg::Vec2f(1000, 1000);
|
.mEnd = TilePosition(1, 1),
|
||||||
manager.setBounds(bounds, nullptr);
|
};
|
||||||
|
manager.setRange(range, nullptr);
|
||||||
manager.setWorldspace(mWorldspace, nullptr);
|
manager.setWorldspace(mWorldspace, nullptr);
|
||||||
|
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
|
@ -452,15 +457,18 @@ namespace
|
||||||
TileCachedRecastMeshManager manager(mSettings);
|
TileCachedRecastMeshManager manager(mSettings);
|
||||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
|
||||||
TileBounds bounds;
|
const TilesPositionsRange range1{
|
||||||
bounds.mMin = osg::Vec2f(182, 0);
|
.mBegin = TilePosition(0, 0),
|
||||||
bounds.mMax = osg::Vec2f(1000, 1000);
|
.mEnd = TilePosition(1, 1),
|
||||||
manager.setBounds(bounds, nullptr);
|
};
|
||||||
|
manager.setRange(range1, nullptr);
|
||||||
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
|
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
|
||||||
bounds.mMin = osg::Vec2f(-1000, -1000);
|
const TilesPositionsRange range2{
|
||||||
bounds.mMax = osg::Vec2f(0, -182);
|
.mBegin = TilePosition(-1, -1),
|
||||||
|
.mEnd = TilePosition(0, 0),
|
||||||
|
};
|
||||||
manager.takeChangedTiles(nullptr);
|
manager.takeChangedTiles(nullptr);
|
||||||
manager.setBounds(bounds, nullptr);
|
manager.setRange(range2, nullptr);
|
||||||
EXPECT_THAT(manager.takeChangedTiles(nullptr),
|
EXPECT_THAT(manager.takeChangedTiles(nullptr),
|
||||||
ElementsAre(
|
ElementsAre(
|
||||||
std::pair(TilePosition(-1, -1), ChangeType::add), std::pair(TilePosition(0, 0), ChangeType::remove)));
|
std::pair(TilePosition(-1, -1), ChangeType::add), std::pair(TilePosition(0, 0), ChangeType::remove)));
|
||||||
|
|
|
@ -41,14 +41,18 @@ namespace DetourNavigator
|
||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
TileBounds makeBounds(const RecastSettings& settings, const osg::Vec2f& center, int maxTiles)
|
TilesPositionsRange makeRange(const TilePosition& center, int maxTiles)
|
||||||
{
|
{
|
||||||
const float radius = fromNavMeshCoordinates(
|
const int radius = static_cast<int>(std::ceil(std::sqrt(static_cast<float>(maxTiles) / osg::PIf) + 1));
|
||||||
settings, std::ceil(std::sqrt(static_cast<float>(maxTiles) / osg::PIf) + 1) * getTileSize(settings));
|
return TilesPositionsRange{
|
||||||
TileBounds result;
|
.mBegin = center - TilePosition(radius, radius),
|
||||||
result.mMin = center - osg::Vec2f(radius, radius);
|
.mEnd = center + TilePosition(radius + 1, radius + 1),
|
||||||
result.mMax = center + osg::Vec2f(radius, radius);
|
};
|
||||||
return result;
|
}
|
||||||
|
|
||||||
|
TilePosition toNavMeshTilePosition(const RecastSettings& settings, const osg::Vec3f& position)
|
||||||
|
{
|
||||||
|
return getTilePosition(settings, toNavMeshCoordinates(settings, position));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,9 +76,9 @@ namespace DetourNavigator
|
||||||
|
|
||||||
void NavMeshManager::updateBounds(const osg::Vec3f& playerPosition, const UpdateGuard* guard)
|
void NavMeshManager::updateBounds(const osg::Vec3f& playerPosition, const UpdateGuard* guard)
|
||||||
{
|
{
|
||||||
const TileBounds bounds = makeBounds(
|
const TilePosition playerTile = toNavMeshTilePosition(mSettings.mRecast, playerPosition);
|
||||||
mSettings.mRecast, osg::Vec2f(playerPosition.x(), playerPosition.y()), mSettings.mMaxTilesNumber);
|
const TilesPositionsRange range = makeRange(playerTile, mSettings.mMaxTilesNumber);
|
||||||
mRecastMeshManager.setBounds(bounds, getImpl(guard));
|
mRecastMeshManager.setRange(range, getImpl(guard));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
|
bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
|
||||||
|
@ -161,15 +165,14 @@ namespace DetourNavigator
|
||||||
|
|
||||||
void NavMeshManager::update(const osg::Vec3f& playerPosition, const UpdateGuard* guard)
|
void NavMeshManager::update(const osg::Vec3f& playerPosition, const UpdateGuard* guard)
|
||||||
{
|
{
|
||||||
const auto playerTile
|
const TilePosition playerTile = toNavMeshTilePosition(mSettings.mRecast, playerPosition);
|
||||||
= getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition));
|
|
||||||
if (mLastRecastMeshManagerRevision == mRecastMeshManager.getRevision() && mPlayerTile.has_value()
|
if (mLastRecastMeshManagerRevision == mRecastMeshManager.getRevision() && mPlayerTile.has_value()
|
||||||
&& *mPlayerTile == playerTile)
|
&& *mPlayerTile == playerTile)
|
||||||
return;
|
return;
|
||||||
mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision();
|
mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision();
|
||||||
mPlayerTile = playerTile;
|
mPlayerTile = playerTile;
|
||||||
const auto changedTiles = mRecastMeshManager.takeChangedTiles(getImpl(guard));
|
const auto changedTiles = mRecastMeshManager.takeChangedTiles(getImpl(guard));
|
||||||
const TilesPositionsRange range = mRecastMeshManager.getRange();
|
const TilesPositionsRange range = mRecastMeshManager.getLimitedObjectsRange();
|
||||||
for (const auto& [agentBounds, cached] : mCache)
|
for (const auto& [agentBounds, cached] : mCache)
|
||||||
update(agentBounds, playerTile, range, cached, changedTiles);
|
update(agentBounds, playerTile, range, cached, changedTiles);
|
||||||
}
|
}
|
||||||
|
@ -179,6 +182,7 @@ namespace DetourNavigator
|
||||||
const std::map<osg::Vec2i, ChangeType>& changedTiles)
|
const std::map<osg::Vec2i, ChangeType>& changedTiles)
|
||||||
{
|
{
|
||||||
std::map<osg::Vec2i, ChangeType> tilesToPost = changedTiles;
|
std::map<osg::Vec2i, ChangeType> tilesToPost = changedTiles;
|
||||||
|
std::map<osg::Vec2i, ChangeType> tilesToPost1;
|
||||||
{
|
{
|
||||||
const auto locked = cached->lockConst();
|
const auto locked = cached->lockConst();
|
||||||
const auto& navMesh = locked->getImpl();
|
const auto& navMesh = locked->getImpl();
|
||||||
|
@ -222,7 +226,7 @@ namespace DetourNavigator
|
||||||
RecastMeshTiles NavMeshManager::getRecastMeshTiles() const
|
RecastMeshTiles NavMeshManager::getRecastMeshTiles() const
|
||||||
{
|
{
|
||||||
RecastMeshTiles result;
|
RecastMeshTiles result;
|
||||||
getTilesPositions(mRecastMeshManager.getRange(), [&](const TilePosition& v) {
|
getTilesPositions(mRecastMeshManager.getLimitedObjectsRange(), [&](const TilePosition& v) {
|
||||||
if (auto mesh = mRecastMeshManager.getCachedMesh(mWorldspace, v))
|
if (auto mesh = mRecastMeshManager.getCachedMesh(mWorldspace, v))
|
||||||
result.emplace(v, std::move(mesh));
|
result.emplace(v, std::move(mesh));
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,9 +18,10 @@ namespace DetourNavigator
|
||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
const TileBounds infiniteTileBounds{ osg::Vec2f(-std::numeric_limits<float>::max(),
|
const TilesPositionsRange infiniteRange{
|
||||||
-std::numeric_limits<float>::max()),
|
.mBegin = TilePosition(std::numeric_limits<int>::min(), std::numeric_limits<int>::min()),
|
||||||
osg::Vec2f(std::numeric_limits<float>::max(), std::numeric_limits<float>::max()) };
|
.mEnd = TilePosition(std::numeric_limits<int>::max(), std::numeric_limits<int>::max()),
|
||||||
|
};
|
||||||
|
|
||||||
struct AddHeightfield
|
struct AddHeightfield
|
||||||
{
|
{
|
||||||
|
@ -57,19 +58,17 @@ namespace DetourNavigator
|
||||||
|
|
||||||
TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings)
|
TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings)
|
||||||
: mSettings(settings)
|
: mSettings(settings)
|
||||||
, mBounds(infiniteTileBounds)
|
, mRange(infiniteRange)
|
||||||
, mRange(makeTilesPositionsRange(mBounds.mMin, mBounds.mMax, mSettings))
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileCachedRecastMeshManager::setBounds(const TileBounds& bounds, const UpdateGuard* guard)
|
void TileCachedRecastMeshManager::setRange(const TilesPositionsRange& range, const UpdateGuard* guard)
|
||||||
{
|
{
|
||||||
if (mBounds == bounds)
|
if (mRange == range)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
const auto newRange = makeTilesPositionsRange(bounds.mMin, bounds.mMax, mSettings);
|
if (mRange != infiniteRange)
|
||||||
if (mBounds != infiniteTileBounds)
|
|
||||||
{
|
{
|
||||||
for (const auto& [id, data] : mObjects)
|
for (const auto& [id, data] : mObjects)
|
||||||
{
|
{
|
||||||
|
@ -77,14 +76,14 @@ namespace DetourNavigator
|
||||||
= makeTilesPositionsRange(data->mObject.getShape(), data->mObject.getTransform(), mSettings);
|
= makeTilesPositionsRange(data->mObject.getShape(), data->mObject.getTransform(), mSettings);
|
||||||
|
|
||||||
getTilesPositions(getIntersection(mRange, objectRange), [&](const TilePosition& v) {
|
getTilesPositions(getIntersection(mRange, objectRange), [&](const TilePosition& v) {
|
||||||
if (!isInTilesPositionsRange(newRange, v))
|
if (!isInTilesPositionsRange(range, v))
|
||||||
{
|
{
|
||||||
addChangedTile(v, ChangeType::remove);
|
addChangedTile(v, ChangeType::remove);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
getTilesPositions(getIntersection(newRange, objectRange), [&](const TilePosition& v) {
|
getTilesPositions(getIntersection(range, objectRange), [&](const TilePosition& v) {
|
||||||
if (!isInTilesPositionsRange(mRange, v))
|
if (!isInTilesPositionsRange(mRange, v))
|
||||||
{
|
{
|
||||||
addChangedTile(v, ChangeType::add);
|
addChangedTile(v, ChangeType::add);
|
||||||
|
@ -100,17 +99,19 @@ namespace DetourNavigator
|
||||||
++mRevision;
|
++mRevision;
|
||||||
}
|
}
|
||||||
|
|
||||||
mBounds = bounds;
|
mRange = range;
|
||||||
mRange = newRange;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TilesPositionsRange TileCachedRecastMeshManager::getRange() const
|
TilesPositionsRange TileCachedRecastMeshManager::getLimitedObjectsRange() const
|
||||||
{
|
{
|
||||||
|
if (mObjects.empty())
|
||||||
|
return {};
|
||||||
const auto bounds = mObjectIndex.bounds();
|
const auto bounds = mObjectIndex.bounds();
|
||||||
return TilesPositionsRange{
|
const TilesPositionsRange objectsRange{
|
||||||
.mBegin = makeTilePosition(bounds.min_corner()),
|
.mBegin = makeTilePosition(bounds.min_corner()),
|
||||||
.mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1),
|
.mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1),
|
||||||
};
|
};
|
||||||
|
return getIntersection(mRange, objectsRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileCachedRecastMeshManager::setWorldspace(const ESM::RefId& worldspace, const UpdateGuard* guard)
|
void TileCachedRecastMeshManager::setWorldspace(const ESM::RefId& worldspace, const UpdateGuard* guard)
|
||||||
|
|
|
@ -44,9 +44,9 @@ namespace DetourNavigator
|
||||||
|
|
||||||
explicit TileCachedRecastMeshManager(const RecastSettings& settings);
|
explicit TileCachedRecastMeshManager(const RecastSettings& settings);
|
||||||
|
|
||||||
void setBounds(const TileBounds& bounds, const UpdateGuard* guard);
|
void setRange(const TilesPositionsRange& range, const UpdateGuard* guard);
|
||||||
|
|
||||||
TilesPositionsRange getRange() const;
|
TilesPositionsRange getLimitedObjectsRange() const;
|
||||||
|
|
||||||
void setWorldspace(const ESM::RefId& worldspace, const UpdateGuard* guard);
|
void setWorldspace(const ESM::RefId& worldspace, const UpdateGuard* guard);
|
||||||
|
|
||||||
|
@ -126,7 +126,6 @@ namespace DetourNavigator
|
||||||
using HeightfieldIndexValue = std::pair<IndexBox, std::map<osg::Vec2i, HeightfieldData>::const_iterator>;
|
using HeightfieldIndexValue = std::pair<IndexBox, std::map<osg::Vec2i, HeightfieldData>::const_iterator>;
|
||||||
|
|
||||||
const RecastSettings& mSettings;
|
const RecastSettings& mSettings;
|
||||||
TileBounds mBounds;
|
|
||||||
TilesPositionsRange mRange;
|
TilesPositionsRange mRange;
|
||||||
ESM::RefId mWorldspace;
|
ESM::RefId mWorldspace;
|
||||||
std::unordered_map<ObjectId, std::unique_ptr<ObjectData>> mObjects;
|
std::unordered_map<ObjectId, std::unique_ptr<ObjectData>> mObjects;
|
||||||
|
|
Loading…
Reference in a new issue