1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-19 12:40:49 +00:00
OpenMW/apps/openmw/mwworld/cellpreloader.cpp
scrawl 29556a1802 More consistent wording of errors/warnings
A Warning indicates a potential problem in the content file(s) that the user told OpenMW to load. E.g. this might cause an object to not display at all or as intended, however the rest of the game will run fine.

An Error, however, is more likely to be a bug with the engine itself - it means that basic assumptions have been violated and the engine might not run correctly anymore.

The above mostly applies to errors/warnings during game-play; startup issues are handled differently: when a file is completely invalid/corrupted to the point that the engine can not start, that might cause messages that are worded as Error due to the severity of the issue but are not necessarily the engine's fault.

Hopefully, being a little more consistent here will alleviate confusion among users as to when a log message should be reported and to whom.
2017-03-04 21:48:31 +01:00

350 lines
11 KiB
C++

#include "cellpreloader.hpp"
#include <iostream>
#include <components/resource/scenemanager.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/bulletshapemanager.hpp>
#include <components/resource/keyframemanager.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/nifosg/nifloader.hpp>
#include <components/terrain/world.hpp>
#include <components/sceneutil/unrefqueue.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/esmstore.hpp"
#include "cellstore.hpp"
#include "manualref.hpp"
#include "class.hpp"
namespace MWWorld
{
struct ListModelsVisitor
{
ListModelsVisitor(std::vector<std::string>& out)
: mOut(out)
{
}
virtual bool operator()(const MWWorld::Ptr& ptr)
{
ptr.getClass().getModelsToPreload(ptr, mOut);
return true;
}
std::vector<std::string>& mOut;
};
/// Worker thread item: preload models in a cell.
class PreloadItem : public SceneUtil::WorkItem
{
public:
/// Constructor to be called from the main thread.
PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager, Terrain::World* terrain, bool preloadInstances)
: mIsExterior(cell->getCell()->isExterior())
, mX(cell->getCell()->getGridX())
, mY(cell->getCell()->getGridY())
, mSceneManager(sceneManager)
, mBulletShapeManager(bulletShapeManager)
, mKeyframeManager(keyframeManager)
, mTerrain(terrain)
, mPreloadInstances(preloadInstances)
, mAbort(false)
{
ListModelsVisitor visitor (mMeshes);
if (cell->getState() == MWWorld::CellStore::State_Loaded)
{
cell->forEach(visitor);
}
else
{
const std::vector<std::string>& 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<std::string>::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);
}
}
}
virtual void abort()
{
mAbort = true;
}
/// Preload work to be called from the worker thread.
virtual void doWork()
{
if (mIsExterior)
{
try
{
mPreloadedObjects.push_back(mTerrain->cacheCell(mX, mY));
}
catch(std::exception& e)
{
}
}
for (MeshList::const_iterator it = mMeshes.begin(); it != mMeshes.end(); ++it)
{
if (mAbort)
break;
try
{
std::string mesh = *it;
mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS());
if (mPreloadInstances)
{
mPreloadedObjects.push_back(mSceneManager->cacheInstance(mesh));
mPreloadedObjects.push_back(mBulletShapeManager->cacheInstance(mesh));
}
else
{
mPreloadedObjects.push_back(mSceneManager->getTemplate(mesh));
mPreloadedObjects.push_back(mBulletShapeManager->getShape(mesh));
}
size_t slashpos = mesh.find_last_of("/\\");
if (slashpos != std::string::npos && slashpos != mesh.size()-1)
{
Misc::StringUtils::lowerCaseInPlace(mesh);
if (mesh[slashpos+1] == 'x')
{
std::string kfname = mesh;
if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0)
{
kfname.replace(kfname.size()-4, 4, ".kf");
mPreloadedObjects.push_back(mKeyframeManager->get(kfname));
}
}
}
}
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<std::string> MeshList;
bool mIsExterior;
int mX;
int mY;
MeshList mMeshes;
Resource::SceneManager* mSceneManager;
Resource::BulletShapeManager* mBulletShapeManager;
Resource::KeyframeManager* mKeyframeManager;
Terrain::World* mTerrain;
bool mPreloadInstances;
volatile bool mAbort;
// keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state
std::vector<osg::ref_ptr<const osg::Object> > mPreloadedObjects;
};
/// Worker thread item: update the resource system's cache, effectively deleting unused entries.
class UpdateCacheItem : public SceneUtil::WorkItem
{
public:
UpdateCacheItem(Resource::ResourceSystem* resourceSystem, Terrain::World* terrain, double referenceTime)
: mReferenceTime(referenceTime)
, mResourceSystem(resourceSystem)
, mTerrain(terrain)
{
}
virtual void doWork()
{
mResourceSystem->updateCache(mReferenceTime);
mTerrain->updateCache();
}
private:
double mReferenceTime;
Resource::ResourceSystem* mResourceSystem;
Terrain::World* mTerrain;
};
CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain)
: mResourceSystem(resourceSystem)
, mBulletShapeManager(bulletShapeManager)
, mTerrain(terrain)
, mExpiryDelay(0.0)
, mMinCacheSize(0)
, mMaxCacheSize(0)
, mPreloadInstances(true)
, mLastResourceCacheUpdate(0.0)
{
}
CellPreloader::~CellPreloader()
{
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it)
{
it->second.mWorkItem->abort();
it->second.mWorkItem->waitTillDone();
}
mPreloadCells.clear();
}
void CellPreloader::preload(CellStore *cell, double timestamp)
{
if (!mWorkQueue)
{
std::cerr << "Error: can't preload, no work queue set " << std::endl;
return;
}
if (cell->getState() == CellStore::State_Unloaded)
{
std::cerr << "Error: 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;
}
while (mPreloadCells.size() >= mMaxCacheSize)
{
// throw out oldest cell to make room
PreloadMap::iterator oldestCell = mPreloadCells.begin();
double oldestTimestamp = DBL_MAX;
double threshold = 1.0; // seconds
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it)
{
if (it->second.mTimeStamp < oldestTimestamp)
{
oldestTimestamp = it->second.mTimeStamp;
oldestCell = it;
}
}
if (oldestTimestamp + threshold < timestamp)
{
oldestCell->second.mWorkItem->abort();
mPreloadCells.erase(oldestCell);
}
else
return;
}
osg::ref_ptr<PreloadItem> item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain, mPreloadInstances));
mWorkQueue->addWorkItem(item);
mPreloadCells[cell] = PreloadEntry(timestamp, item);
}
void CellPreloader::notifyLoaded(CellStore *cell)
{
PreloadMap::iterator found = mPreloadCells.find(cell);
if (found != mPreloadCells.end())
{
// do the deletion in the background thread
if (found->second.mWorkItem)
{
found->second.mWorkItem->abort();
mUnrefQueue->push(mPreloadCells[cell].mWorkItem);
}
mPreloadCells.erase(found);
}
}
void CellPreloader::clear()
{
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();)
{
if (it->second.mWorkItem)
{
it->second.mWorkItem->abort();
mUnrefQueue->push(it->second.mWorkItem);
}
mPreloadCells.erase(it++);
}
}
void CellPreloader::updateCache(double timestamp)
{
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();)
{
if (mPreloadCells.size() >= mMinCacheSize && it->second.mTimeStamp < timestamp - mExpiryDelay)
{
if (it->second.mWorkItem)
{
it->second.mWorkItem->abort();
mUnrefQueue->push(it->second.mWorkItem);
}
mPreloadCells.erase(it++);
}
else
++it;
}
if (timestamp - mLastResourceCacheUpdate > 1.0)
{
// the resource cache is cleared from the worker thread so that we're not holding up the main thread with delete operations
mWorkQueue->addWorkItem(new UpdateCacheItem(mResourceSystem, mTerrain, timestamp), true);
mLastResourceCacheUpdate = timestamp;
}
}
void CellPreloader::setExpiryDelay(double expiryDelay)
{
mExpiryDelay = expiryDelay;
}
void CellPreloader::setMinCacheSize(unsigned int num)
{
mMinCacheSize = num;
}
void CellPreloader::setMaxCacheSize(unsigned int num)
{
mMaxCacheSize = num;
}
void CellPreloader::setPreloadInstances(bool preload)
{
mPreloadInstances = preload;
}
unsigned int CellPreloader::getMaxCacheSize() const
{
return mMaxCacheSize;
}
void CellPreloader::setWorkQueue(osg::ref_ptr<SceneUtil::WorkQueue> workQueue)
{
mWorkQueue = workQueue;
}
void CellPreloader::setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue)
{
mUnrefQueue = unrefQueue;
}
}