From 24ce242941a13316fde09eb0d5ffc818a76ddb0c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Nov 2019 17:40:19 +0400 Subject: [PATCH] Implement TestCells (feature #5219) --- CHANGELOG.md | 1 + apps/openmw/mwbase/world.hpp | 3 + apps/openmw/mwrender/renderingmanager.cpp | 6 +- apps/openmw/mwrender/renderingmanager.hpp | 4 + apps/openmw/mwscript/cellextensions.cpp | 56 ++++++- apps/openmw/mwscript/docs/vmformat.txt | 4 +- apps/openmw/mwworld/scene.cpp | 178 +++++++++++++++++----- apps/openmw/mwworld/scene.hpp | 9 +- apps/openmw/mwworld/store.cpp | 8 + apps/openmw/mwworld/store.hpp | 2 + apps/openmw/mwworld/worldimp.cpp | 10 ++ apps/openmw/mwworld/worldimp.hpp | 3 + components/compiler/extensions0.cpp | 2 + components/compiler/opcodes.hpp | 2 + 14 files changed, 246 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 216547668..9b50fdff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -235,6 +235,7 @@ Feature #5147: Show spell magicka cost in spell buying window Feature #5170: Editor: Land shape editing, land selection Feature #5193: Weapon sheathing + Feature #5219: Impelement TestCells console command Feature #5224: Handle NiKeyframeController for NiTriShape Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4695: Optimize Distant Terrain memory consumption diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index a2e36d3d0..0529140f4 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -118,6 +118,9 @@ namespace MWBase virtual MWWorld::CellStore *getCell (const ESM::CellId& id) = 0; + virtual void testExteriorCells() = 0; + virtual void testInteriorCells() = 0; + virtual void useDeathCamera() = 0; virtual void setWaterHeight(const float height) = 0; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ac4fb3a16..8f7b339b8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -15,7 +15,6 @@ #include #include -#include #include @@ -391,6 +390,11 @@ namespace MWRender mWorkQueue = nullptr; } + osgUtil::IncrementalCompileOperation* RenderingManager::getIncrementalCompileOperation() + { + return mViewer->getIncrementalCompileOperation(); + } + MWRender::Objects& RenderingManager::getObjects() { return *mObjects.get(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index fb94caec8..22d2cf28c 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -7,6 +7,8 @@ #include +#include + #include "objects.hpp" #include "renderinginterface.hpp" @@ -89,6 +91,8 @@ namespace MWRender const std::string& resourcePath, DetourNavigator::Navigator& navigator); ~RenderingManager(); + osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation(); + MWRender::Objects& getObjects(); Resource::ResourceSystem* getResourceSystem(); diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 80d4f7eb8..13ef98c63 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -10,11 +10,13 @@ #include #include +#include "../mwworld/actionteleport.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwworld/player.hpp" -#include "../mwworld/cellstore.hpp" -#include "../mwworld/actionteleport.hpp" +#include "../mwbase/statemanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" @@ -34,6 +36,52 @@ namespace MWScript } }; + class OpTestCells : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame) + { + runtime.getContext().report("Use TestCells from the main menu, when there is no active game session."); + return; + } + + bool wasConsole = MWBase::Environment::get().getWindowManager()->isConsoleMode(); + if (wasConsole) + MWBase::Environment::get().getWindowManager()->toggleConsole(); + + MWBase::Environment::get().getWorld()->testExteriorCells(); + + if (wasConsole) + MWBase::Environment::get().getWindowManager()->toggleConsole(); + } + }; + + class OpTestInteriorCells : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame) + { + runtime.getContext().report("Use TestInteriorCells from the main menu, when there is no active game session."); + return; + } + + bool wasConsole = MWBase::Environment::get().getWindowManager()->isConsoleMode(); + if (wasConsole) + MWBase::Environment::get().getWindowManager()->toggleConsole(); + + MWBase::Environment::get().getWorld()->testInteriorCells(); + + if (wasConsole) + MWBase::Environment::get().getWindowManager()->toggleConsole(); + } + }; + class OpCOC : public Interpreter::Opcode0 { public: @@ -204,6 +252,8 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Cell::opcodeCellChanged, new OpCellChanged); + interpreter.installSegment5 (Compiler::Cell::opcodeTestCells, new OpTestCells); + interpreter.installSegment5 (Compiler::Cell::opcodeTestInteriorCells, new OpTestInteriorCells); interpreter.installSegment5 (Compiler::Cell::opcodeCOC, new OpCOC); interpreter.installSegment5 (Compiler::Cell::opcodeCOE, new OpCOE); interpreter.installSegment5 (Compiler::Cell::opcodeGetInterior, new OpGetInterior); diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index b3029f417..6795a058f 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -461,5 +461,7 @@ op 0x200030a: SetNavMeshNumber op 0x200030b: Journal, explicit op 0x200030c: RepairedOnMe op 0x200030d: RepairedOnMe, explicit +op 0x200030e: TestCells +op 0x200030f: TestInteriorCells -opcodes 0x200030c-0x3ffffff unused +opcodes 0x2000310-0x3ffffff unused diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 0d1b98ebb..4de04251b 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,8 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "../mwrender/renderingmanager.hpp" #include "../mwrender/landmanager.hpp" @@ -205,10 +208,11 @@ namespace { MWWorld::CellStore& mCell; Loading::Listener& mLoadingListener; + bool mTest; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener); + InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test); bool operator() (const MWWorld::Ptr& ptr); @@ -216,8 +220,8 @@ namespace void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener) - : mCell (cell), mLoadingListener (loadingListener) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test) + : mCell (cell), mLoadingListener (loadingListener), mTest(test) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -246,7 +250,8 @@ namespace } } - mLoadingListener.increaseProgress (1); + if (!mTest) + mLoadingListener.increaseProgress (1); } } @@ -317,9 +322,10 @@ namespace MWWorld mPreloader->updateCache(mRendering.getReferenceTime()); } - void Scene::unloadCell (CellStoreCollection::iterator iter) + void Scene::unloadCell (CellStoreCollection::iterator iter, bool test) { - Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); + if (!test) + Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); ListAndResetObjectsVisitor visitor; @@ -373,13 +379,16 @@ namespace MWWorld mActiveCells.erase(*iter); } - void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn) + void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) { std::pair result = mActiveCells.insert(cell); if(result.second) { - Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); + if (test) + Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); + else + Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; @@ -390,7 +399,7 @@ namespace MWWorld const int cellY = cell->getCell()->getGridY(); // Load terrain physics first... - if (cell->getCell()->isExterior()) + if (!test && cell->getCell()->isExterior()) { osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0; @@ -418,38 +427,44 @@ namespace MWWorld cell->respawn(); // ... then references. This is important for adjustPosition to work correctly. - insertCell (*cell, loadingListener); + insertCell (*cell, loadingListener, test); mRendering.addCell(cell); - MWBase::Environment::get().getWindowManager()->addCell(cell); - bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); - float waterLevel = cell->getWaterLevel(); - mRendering.setWaterEnabled(waterEnabled); - if (waterEnabled) + if (!test) { - mPhysics->enableWater(waterLevel); - mRendering.setWaterHeight(waterLevel); - - if (cell->getCell()->isExterior()) + MWBase::Environment::get().getWindowManager()->addCell(cell); + bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); + float waterLevel = cell->getWaterLevel(); + mRendering.setWaterEnabled(waterEnabled); + if (waterEnabled) { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, - cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); + mPhysics->enableWater(waterLevel); + mRendering.setWaterHeight(waterLevel); + + if (cell->getCell()->isExterior()) + { + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, + cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); + } + else + { + navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), + cell->getWaterLevel(), btTransform::getIdentity()); + } } else + mPhysics->disableWater(); + + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + navigator->update(player.getRefData().getPosition().asVec3()); + + if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) { - navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), - cell->getWaterLevel(), btTransform::getIdentity()); + + mRendering.configureAmbient(cell->getCell()); } } - else - mPhysics->disableWater(); - - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - navigator->update(player.getRefData().getPosition().asVec3()); - - if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) - mRendering.configureAmbient(cell->getCell()); } mPreloader->notifyLoaded(cell); @@ -594,6 +609,101 @@ namespace MWWorld mCellChanged = true; } + void Scene::testExteriorCells() + { + // Note: temporary disable ICO to decrease memory usage + mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(nullptr); + + mRendering.getResourceSystem()->setExpiryDelay(1.f); + + const MWWorld::Store &cells = MWBase::Environment::get().getWorld()->getStore().get(); + + Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + Loading::ScopedLoad load(loadingListener); + loadingListener->setProgressRange(cells.getExtSize()); + + MWWorld::Store::iterator it = cells.extBegin(); + int i = 1; + for (; it != cells.extEnd(); ++it) + { + loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")..."); + + CellStoreCollection::iterator iter = mActiveCells.begin(); + + CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); + loadCell (cell, loadingListener, false, true); + + iter = mActiveCells.begin(); + while (iter != mActiveCells.end()) + { + if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && + it->mData.mY == (*iter)->getCell()->getGridY()) + { + unloadCell(iter, true); + break; + } + + ++iter; + } + + mRendering.getResourceSystem()->updateCache(mRendering.getReferenceTime()); + mRendering.getUnrefQueue()->flush(mRendering.getWorkQueue()); + + loadingListener->increaseProgress (1); + i++; + } + + mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(mRendering.getIncrementalCompileOperation()); + mRendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells")); + } + + void Scene::testInteriorCells() + { + // Note: temporary disable ICO to decrease memory usage + mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(nullptr); + + mRendering.getResourceSystem()->setExpiryDelay(1.f); + + const MWWorld::Store &cells = MWBase::Environment::get().getWorld()->getStore().get(); + + Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + Loading::ScopedLoad load(loadingListener); + loadingListener->setProgressRange(cells.getIntSize()); + + int i = 1; + MWWorld::Store::iterator it = cells.intBegin(); + for (; it != cells.intEnd(); ++it) + { + loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); + + CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); + loadCell (cell, loadingListener, false, true); + + CellStoreCollection::iterator iter = mActiveCells.begin(); + while (iter != mActiveCells.end()) + { + assert (!(*iter)->getCell()->isExterior()); + + if (it->mName == (*iter)->getCell()->mName) + { + unloadCell(iter, true); + break; + } + + ++iter; + } + + mRendering.getResourceSystem()->updateCache(mRendering.getReferenceTime()); + mRendering.getUnrefQueue()->flush(mRendering.getWorkQueue()); + + loadingListener->increaseProgress (1); + i++; + } + + mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(mRendering.getIncrementalCompileOperation()); + mRendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells")); + } + void Scene::changePlayerCell(CellStore *cell, const ESM::Position &pos, bool adjustPlayerPos) { mCurrentCell = cell; @@ -759,9 +869,9 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener) + void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test) { - InsertVisitor insertVisitor (cell, *loadingListener); + InsertVisitor insertVisitor (cell, *loadingListener, test); cell.forEach (insertVisitor); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering); }); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 57f994fab..da795f84b 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -85,7 +85,7 @@ namespace MWWorld osg::Vec3f mLastPlayerPos; - void insertCell (CellStore &cell, Loading::Listener* loadingListener); + void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false); // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center void changeCellGrid (int playerCellX, int playerCellY, bool changeEvent = true); @@ -107,9 +107,9 @@ namespace MWWorld void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false); void preloadTerrain(const osg::Vec3f& pos); - void unloadCell (CellStoreCollection::iterator iter); + void unloadCell (CellStoreCollection::iterator iter, bool test = false); - void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn); + void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); void playerMoved (const osg::Vec3f& pos); @@ -151,6 +151,9 @@ namespace MWWorld Ptr searchPtrViaActorId (int actorId); void preload(const std::string& mesh, bool useAnim=false); + + void testExteriorCells(); + void testInteriorCells(); }; } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 8ceab26b6..a414585ef 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -792,6 +792,14 @@ namespace MWWorld { return mSharedInt.size() + mSharedExt.size(); } + size_t Store::getExtSize() const + { + return mSharedExt.size(); + } + size_t Store::getIntSize() const + { + return mSharedInt.size(); + } void Store::listIdentifier(std::vector &list) const { list.reserve(list.size() + mSharedInt.size()); diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 4162e84c5..e7f2d35db 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -314,6 +314,8 @@ namespace MWWorld const ESM::Cell *searchExtByRegion(const std::string &id) const; size_t getSize() const; + size_t getExtSize() const; + size_t getIntSize() const; void listIdentifier(std::vector &list) const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 2ac27f0b7..71948119a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -586,6 +586,16 @@ namespace MWWorld return getInterior (id.mWorldspace); } + void World::testExteriorCells() + { + mWorldScene->testExteriorCells(); + } + + void World::testInteriorCells() + { + mWorldScene->testInteriorCells(); + } + void World::useDeathCamera() { if(mRendering->getCamera()->isVanityOrPreviewModeEnabled() ) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 6090e2ce4..ed622b5b8 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -223,6 +223,9 @@ namespace MWWorld CellStore *getCell (const ESM::CellId& id) override; + void testExteriorCells() override; + void testInteriorCells() override; + //switch to POV before showing player's death animation void useDeathCamera() override; diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 0c7b0e26b..5bec1bca1 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -89,6 +89,8 @@ namespace Compiler void registerExtensions (Extensions& extensions) { extensions.registerFunction ("cellchanged", 'l', "", opcodeCellChanged); + extensions.registerInstruction("testcells", "", opcodeTestCells); + extensions.registerInstruction("testinteriorcells", "", opcodeTestInteriorCells); extensions.registerInstruction ("coc", "S", opcodeCOC); extensions.registerInstruction ("centeroncell", "S", opcodeCOC); extensions.registerInstruction ("coe", "ll", opcodeCOE); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 47686d45e..5fa8cc170 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -75,6 +75,8 @@ namespace Compiler namespace Cell { const int opcodeCellChanged = 0x2000000; + const int opcodeTestCells = 0x200030e; + const int opcodeTestInteriorCells = 0x200030f; const int opcodeCOC = 0x2000026; const int opcodeCOE = 0x2000226; const int opcodeGetInterior = 0x2000131;