From 6f9ca0f68fd76bfd1abef2f9e5ae0131b8a64e89 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 00:07:02 +0100 Subject: [PATCH] Add basic cell preloader class Not properly in use yet, but seems to be working. --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/mwphysics/physicssystem.cpp | 5 + apps/openmw/mwphysics/physicssystem.hpp | 2 + apps/openmw/mwworld/cellpreloader.cpp | 148 ++++++++++++++++++++++++ apps/openmw/mwworld/cellpreloader.hpp | 60 ++++++++++ apps/openmw/mwworld/cellstore.cpp | 5 + apps/openmw/mwworld/cellstore.hpp | 3 + components/CMakeLists.txt | 4 +- components/resource/resourcesystem.cpp | 3 + 9 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 apps/openmw/mwworld/cellpreloader.cpp create mode 100644 apps/openmw/mwworld/cellpreloader.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 02cf6c87d..f2540c739 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -67,6 +67,7 @@ add_openmw_dir (mwworld actionequip timestamp actionalchemy cellstore actionapply actioneat store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager + cellpreloader ) add_openmw_dir (mwphysics diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 11d6d286a..6417968d3 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -690,6 +690,11 @@ namespace MWPhysics delete mBroadphase; } + Resource::BulletShapeManager *PhysicsSystem::getShapeManager() + { + return mShapeManager.get(); + } + bool PhysicsSystem::toggleDebugRendering() { mDebugDrawEnabled = !mDebugDrawEnabled; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 4263bc0ec..a866717c7 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -53,6 +53,8 @@ namespace MWPhysics PhysicsSystem (Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode); ~PhysicsSystem (); + Resource::BulletShapeManager* getShapeManager(); + void enableWater(float height); void setWaterHeight(float height); void disableWater(); diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp new file mode 100644 index 000000000..dd6850aba --- /dev/null +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -0,0 +1,148 @@ +#include "cellpreloader.hpp" + +#include + +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "cellstore.hpp" +#include "manualref.hpp" +#include "class.hpp" + +namespace MWWorld +{ + + struct ListModelsVisitor + { + ListModelsVisitor(std::vector& out) + : mOut(out) + { + } + + virtual bool operator()(const MWWorld::ConstPtr& ptr) + { + std::string model = ptr.getClass().getModel(ptr); + if (!model.empty()) + mOut.push_back(model); + return true; + } + + std::vector& mOut; + }; + + class PreloadItem : public SceneUtil::WorkItem + { + public: + /// Constructor to be called from the main thread. + PreloadItem(const MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager) + : mSceneManager(sceneManager) + , mBulletShapeManager(bulletShapeManager) + { + if (cell->getState() == MWWorld::CellStore::State_Loaded) + { + ListModelsVisitor visitor (mMeshes); + cell->forEachConst(visitor); + } + else + { + const std::vector& objectIds = cell->getPreloadedIds(); + + // could possibly build the model list in the worker thread if we manage to make the Store thread safe + for (std::vector::const_iterator it = objectIds.begin(); it != objectIds.end(); ++it) + { + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), *it); + std::string model = ref.getPtr().getClass().getModel(ref.getPtr()); + if (!model.empty()) + mMeshes.push_back(model); + } + } + } + + /// Preload work to be called from the worker thread. + virtual void doWork() + { + for (MeshList::const_iterator it = mMeshes.begin(); it != mMeshes.end(); ++it) + { + try + { + mPreloadedNodes.push_back(mSceneManager->getTemplate(*it)); + mPreloadedShapes.push_back(mBulletShapeManager->getShape(*it)); + + // TODO: do a createInstance() and hold on to it since we can make use of it when the cell goes active + } + catch (std::exception& e) + { + // ignore error for now, would spam the log too much + // error will be shown when visiting the cell + } + } + } + + private: + typedef std::vector MeshList; + MeshList mMeshes; + Resource::SceneManager* mSceneManager; + Resource::BulletShapeManager* mBulletShapeManager; + + // keep a ref to the loaded object to make sure it stays loaded as long as this cell is in the preloaded state + std::vector > mPreloadedNodes; + std::vector > mPreloadedShapes; + }; + + CellPreloader::CellPreloader(Resource::SceneManager *sceneManager, Resource::BulletShapeManager* bulletShapeManager) + : mSceneManager(sceneManager) + , mBulletShapeManager(bulletShapeManager) + { + mWorkQueue = new SceneUtil::WorkQueue; + } + + void CellPreloader::preload(const CellStore *cell, double timestamp) + { + if (!mWorkQueue) + { + std::cerr << "can't preload, no work queue set " << std::endl; + return; + } + if (cell->getState() == CellStore::State_Unloaded) + { + std::cerr << "can't preload objects for unloaded cell" << std::endl; + return; + } + + PreloadMap::iterator found = mPreloadCells.find(cell); + if (found != mPreloadCells.end()) + { + // already preloaded, nothing to do other than updating the timestamp + found->second.mTimeStamp = timestamp; + return; + } + + osg::ref_ptr item (new PreloadItem(cell, mSceneManager, mBulletShapeManager)); + mWorkQueue->addWorkItem(item); + + mPreloadCells[cell] = PreloadEntry(timestamp, item); + } + + void CellPreloader::updateCache(double timestamp) + { + // time (in seconds) to keep a preloaded cell in cache after it's no longer needed + const double expiryTime = 60.0; + + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();) + { + if (it->second.mTimeStamp < timestamp - expiryTime) + mPreloadCells.erase(it++); + else + ++it; + } + } + + void CellPreloader::setWorkQueue(osg::ref_ptr workQueue) + { + mWorkQueue = workQueue; + } + +} diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp new file mode 100644 index 000000000..7e02cb7d1 --- /dev/null +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -0,0 +1,60 @@ +#ifndef OPENMW_MWWORLD_CELLPRELOADER_H +#define OPENMW_MWWORLD_CELLPRELOADER_H + +#include +#include +#include + +namespace Resource +{ + class SceneManager; + class BulletShapeManager; +} + +namespace MWWorld +{ + class CellStore; + + class CellPreloader + { + public: + CellPreloader(Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager); + + /// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. + /// @note The cell itself must be in State_Loaded or State_Preloaded. + void preload(const MWWorld::CellStore* cell, double timestamp); + + /// Removes preloaded cells that have not had a preload request for a while. + void updateCache(double timestamp); + + void setWorkQueue(osg::ref_ptr workQueue); + + private: + Resource::SceneManager* mSceneManager; + Resource::BulletShapeManager* mBulletShapeManager; + osg::ref_ptr mWorkQueue; + + struct PreloadEntry + { + PreloadEntry(double timestamp, osg::ref_ptr workItem) + : mTimeStamp(timestamp) + , mWorkItem(workItem) + { + } + PreloadEntry() + : mTimeStamp(0.0) + { + } + + double mTimeStamp; + osg::ref_ptr mWorkItem; + }; + typedef std::map PreloadMap; + + // Cells that are currently being preloaded, or have already finished preloading + PreloadMap mPreloadCells; + }; + +} + +#endif diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 5c8d07f86..dcaa73e93 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -333,6 +333,11 @@ namespace MWWorld return mState; } + const std::vector &CellStore::getPreloadedIds() const + { + return mIds; + } + bool CellStore::hasState() const { return mHasState; diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 2f483f0ba..d753c9745 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -204,6 +204,9 @@ namespace MWWorld State getState() const; + const std::vector& getPreloadedIds() const; + ///< Get Ids of objects in this cell, only valid in State_Preloaded + bool hasState() const; ///< Does this cell have state that needs to be stored in a saved game file? diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 42323a68a..26e8f7b6a 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -46,9 +46,7 @@ add_component_dir (resource add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller - lightmanager lightutil positionattitudetransform - # not used yet - #workqueue + lightmanager lightutil positionattitudetransform workqueue ) add_component_dir (nif diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index f499c0016..68ed13644 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -54,6 +54,9 @@ namespace Resource void ResourceSystem::updateCache(double referenceTime) { + // TODO: call updateCache from the worker thread so the main thread isn't held up by the delete operations + // change ObjectCache to not hold lock while the unref happens + osg::Timer timer; for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->updateCache(referenceTime);