mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-27 23:45:38 +00:00
refactors parentFileIndices (#3211)
This PR aims to start addressing `ESM` design issues that have silenced errors we incorporated into groundcover `ESM` loading approaches. - We move the resolution of `parentFileIndices` from `ESMStore` to `ESMReader` as suggested in a `TODO` comment. - We improve a highly misleading comment which downplayed the significance of `parentFileIndices`. - We document important preconditions. - We move a user facing error message to the highest level and improve its context. - We remove an inappropriate `setGlobalReaderList` method. We now pass this reader list into the method that requires it. - We remove a thoroughly pointless optimisation of `Store<ESM::LandTexture>`'s construction that has unnecessarily depended on `getGlobalReaderList`. There should be no functional changes for `master`, but this PR should remove an issue blocking PR #3208.
This commit is contained in:
parent
a5bfec610c
commit
4657c655b1
9 changed files with 60 additions and 63 deletions
apps
openmw/mwworld
openmw_test_suite/mwworld
components
|
@ -42,7 +42,7 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index)
|
||||||
ESM::ESMReader lEsm;
|
ESM::ESMReader lEsm;
|
||||||
lEsm.setEncoder(mEncoder);
|
lEsm.setEncoder(mEncoder);
|
||||||
lEsm.setIndex(index);
|
lEsm.setIndex(index);
|
||||||
lEsm.setGlobalReaderList(&mEsm);
|
lEsm.resolveParentFileIndices(mEsm);
|
||||||
lEsm.open(filepath.string());
|
lEsm.open(filepath.string());
|
||||||
mEsm[index] = lEsm;
|
mEsm[index] = lEsm;
|
||||||
mStore.load(mEsm[index], &mListener);
|
mStore.load(mEsm[index], &mListener);
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include <boost/filesystem/operations.hpp>
|
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/esm/esmreader.hpp>
|
#include <components/esm/esmreader.hpp>
|
||||||
#include <components/esm/esmwriter.hpp>
|
#include <components/esm/esmwriter.hpp>
|
||||||
|
@ -153,41 +151,9 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener)
|
||||||
ESM::Dialogue *dialogue = nullptr;
|
ESM::Dialogue *dialogue = nullptr;
|
||||||
|
|
||||||
// Land texture loading needs to use a separate internal store for each plugin.
|
// Land texture loading needs to use a separate internal store for each plugin.
|
||||||
// We set the number of plugins here to avoid continual resizes during loading,
|
// We set the number of plugins here so we can properly verify if valid plugin
|
||||||
// and so we can properly verify if valid plugin indices are being passed to the
|
// indices are being passed to the LandTexture Store retrieval methods.
|
||||||
// LandTexture Store retrieval methods.
|
mLandTextures.resize(mLandTextures.getSize()+1);
|
||||||
mLandTextures.resize(esm.getGlobalReaderList()->size());
|
|
||||||
|
|
||||||
/// \todo Move this to somewhere else. ESMReader?
|
|
||||||
// Cache parent esX files by tracking their indices in the global list of
|
|
||||||
// all files/readers used by the engine. This will greaty accelerate
|
|
||||||
// refnumber mangling, as required for handling moved references.
|
|
||||||
const std::vector<ESM::Header::MasterData> &masters = esm.getGameFiles();
|
|
||||||
std::vector<ESM::ESMReader> *allPlugins = esm.getGlobalReaderList();
|
|
||||||
for (size_t j = 0; j < masters.size(); j++) {
|
|
||||||
const ESM::Header::MasterData &mast = masters[j];
|
|
||||||
std::string fname = mast.name;
|
|
||||||
int index = ~0;
|
|
||||||
for (int i = 0; i < esm.getIndex(); i++) {
|
|
||||||
ESM::ESMReader& reader = allPlugins->at(i);
|
|
||||||
if (reader.getFileSize() == 0)
|
|
||||||
continue; // Content file in non-ESM format
|
|
||||||
const std::string candidate = reader.getContext().filename;
|
|
||||||
std::string fnamecandidate = boost::filesystem::path(candidate).filename().string();
|
|
||||||
if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) {
|
|
||||||
index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (index == (int)~0) {
|
|
||||||
// Tried to load a parent file that has not been loaded yet. This is bad,
|
|
||||||
// the launcher should have taken care of this.
|
|
||||||
std::string fstring = "File " + esm.getName() + " asks for parent file " + masters[j].name
|
|
||||||
+ ", but it has not been loaded yet. Please check your load order.";
|
|
||||||
esm.fail(fstring);
|
|
||||||
}
|
|
||||||
esm.addParentFileIndex(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop through all records
|
// Loop through all records
|
||||||
while(esm.hasMoreRecs())
|
while(esm.hasMoreRecs())
|
||||||
|
|
|
@ -293,11 +293,6 @@ namespace MWWorld
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
Store<ESM::LandTexture>::Store()
|
Store<ESM::LandTexture>::Store()
|
||||||
{
|
{
|
||||||
mStatic.emplace_back();
|
|
||||||
LandTextureList <exl = mStatic[0];
|
|
||||||
// More than enough to hold Morrowind.esm. Extra lists for plugins will we
|
|
||||||
// added on-the-fly in a different method.
|
|
||||||
ltexl.reserve(128);
|
|
||||||
}
|
}
|
||||||
const ESM::LandTexture *Store<ESM::LandTexture>::search(size_t index, size_t plugin) const
|
const ESM::LandTexture *Store<ESM::LandTexture>::search(size_t index, size_t plugin) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -178,6 +178,10 @@ namespace MWWorld
|
||||||
if (mEsm[0].getFormat() == 0)
|
if (mEsm[0].getFormat() == 0)
|
||||||
ensureNeededRecords();
|
ensureNeededRecords();
|
||||||
|
|
||||||
|
// TODO: We can and should validate before we call loadContentFiles().
|
||||||
|
// Currently we validate here to prevent merge conflicts with groundcover ESMStore fixes.
|
||||||
|
validateMasterFiles(mEsm);
|
||||||
|
|
||||||
mCurrentDate.reset(new DateTimeManager());
|
mCurrentDate.reset(new DateTimeManager());
|
||||||
|
|
||||||
fillGlobalVariables();
|
fillGlobalVariables();
|
||||||
|
@ -407,6 +411,23 @@ namespace MWWorld
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void World::validateMasterFiles(const std::vector<ESM::ESMReader>& readers)
|
||||||
|
{
|
||||||
|
for (const auto& esm : readers)
|
||||||
|
{
|
||||||
|
assert(esm.getGameFiles().size() == esm.getParentFileIndices().size());
|
||||||
|
for (unsigned int i=0; i<esm.getParentFileIndices().size(); ++i)
|
||||||
|
{
|
||||||
|
if (!esm.isValidParentFileIndex(i))
|
||||||
|
{
|
||||||
|
std::string fstring = "File " + esm.getName() + " asks for parent file " + esm.getGameFiles()[i].name
|
||||||
|
+ ", but it is not available or has been loaded in the wrong order. Please run the launcher to fix this issue.";
|
||||||
|
throw std::runtime_error(fstring);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void World::ensureNeededRecords()
|
void World::ensureNeededRecords()
|
||||||
{
|
{
|
||||||
std::map<std::string, ESM::Variant> gmst;
|
std::map<std::string, ESM::Variant> gmst;
|
||||||
|
|
|
@ -157,6 +157,7 @@ namespace MWWorld
|
||||||
void updateNavigatorObject(const MWPhysics::Object& object);
|
void updateNavigatorObject(const MWPhysics::Object& object);
|
||||||
|
|
||||||
void ensureNeededRecords();
|
void ensureNeededRecords();
|
||||||
|
void validateMasterFiles(const std::vector<ESM::ESMReader>& readers);
|
||||||
|
|
||||||
void fillGlobalVariables();
|
void fillGlobalVariables();
|
||||||
|
|
||||||
|
|
|
@ -29,19 +29,14 @@ struct ContentFileTest : public ::testing::Test
|
||||||
readContentFiles();
|
readContentFiles();
|
||||||
|
|
||||||
// load the content files
|
// load the content files
|
||||||
std::vector<ESM::ESMReader> readerList;
|
|
||||||
readerList.resize(mContentFiles.size());
|
|
||||||
|
|
||||||
int index=0;
|
int index=0;
|
||||||
for (const auto & mContentFile : mContentFiles)
|
for (const auto & mContentFile : mContentFiles)
|
||||||
{
|
{
|
||||||
ESM::ESMReader lEsm;
|
ESM::ESMReader lEsm;
|
||||||
lEsm.setEncoder(nullptr);
|
lEsm.setEncoder(nullptr);
|
||||||
lEsm.setIndex(index);
|
lEsm.setIndex(index);
|
||||||
lEsm.setGlobalReaderList(&readerList);
|
|
||||||
lEsm.open(mContentFile.string());
|
lEsm.open(mContentFile.string());
|
||||||
readerList[index] = lEsm;
|
mEsmStore.load(lEsm, &dummyListener);
|
||||||
mEsmStore.load(readerList[index], &dummyListener);
|
|
||||||
|
|
||||||
++index;
|
++index;
|
||||||
}
|
}
|
||||||
|
@ -254,9 +249,6 @@ TEST_F(StoreTest, delete_test)
|
||||||
record.mId = recordId;
|
record.mId = recordId;
|
||||||
|
|
||||||
ESM::ESMReader reader;
|
ESM::ESMReader reader;
|
||||||
std::vector<ESM::ESMReader> readerList;
|
|
||||||
readerList.push_back(reader);
|
|
||||||
reader.setGlobalReaderList(&readerList);
|
|
||||||
|
|
||||||
// master file inserts a record
|
// master file inserts a record
|
||||||
Files::IStreamPtr file = getEsmFile(record, false);
|
Files::IStreamPtr file = getEsmFile(record, false);
|
||||||
|
@ -297,9 +289,6 @@ TEST_F(StoreTest, overwrite_test)
|
||||||
record.mId = recordId;
|
record.mId = recordId;
|
||||||
|
|
||||||
ESM::ESMReader reader;
|
ESM::ESMReader reader;
|
||||||
std::vector<ESM::ESMReader> readerList;
|
|
||||||
readerList.push_back(reader);
|
|
||||||
reader.setGlobalReaderList(&readerList);
|
|
||||||
|
|
||||||
// master file inserts a record
|
// master file inserts a record
|
||||||
Files::IStreamPtr file = getEsmFile(record, false);
|
Files::IStreamPtr file = getEsmFile(record, false);
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#include "esmreader.hpp"
|
#include "esmreader.hpp"
|
||||||
|
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
|
@ -17,7 +20,6 @@ ESM_Context ESMReader::getContext()
|
||||||
ESMReader::ESMReader()
|
ESMReader::ESMReader()
|
||||||
: mRecordFlags(0)
|
: mRecordFlags(0)
|
||||||
, mBuffer(50*1024)
|
, mBuffer(50*1024)
|
||||||
, mGlobalReaderList(nullptr)
|
|
||||||
, mEncoder(nullptr)
|
, mEncoder(nullptr)
|
||||||
, mFileSize(0)
|
, mFileSize(0)
|
||||||
{
|
{
|
||||||
|
@ -55,6 +57,29 @@ void ESMReader::clearCtx()
|
||||||
mCtx.subName.clear();
|
mCtx.subName.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ESMReader::resolveParentFileIndices(const std::vector<ESMReader>& allPlugins)
|
||||||
|
{
|
||||||
|
mCtx.parentFileIndices.clear();
|
||||||
|
const std::vector<Header::MasterData> &masters = getGameFiles();
|
||||||
|
for (size_t j = 0; j < masters.size(); j++) {
|
||||||
|
const Header::MasterData &mast = masters[j];
|
||||||
|
std::string fname = mast.name;
|
||||||
|
int index = getIndex();
|
||||||
|
for (int i = 0; i < getIndex(); i++) {
|
||||||
|
const ESMReader& reader = allPlugins.at(i);
|
||||||
|
if (reader.getFileSize() == 0)
|
||||||
|
continue; // Content file in non-ESM format
|
||||||
|
const std::string candidate = reader.getName();
|
||||||
|
std::string fnamecandidate = boost::filesystem::path(candidate).filename().string();
|
||||||
|
if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mCtx.parentFileIndices.push_back(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name)
|
void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name)
|
||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
|
|
|
@ -80,13 +80,15 @@ public:
|
||||||
// to the individual load() methods. This hack allows to pass this reference
|
// to the individual load() methods. This hack allows to pass this reference
|
||||||
// indirectly to the load() method.
|
// indirectly to the load() method.
|
||||||
void setIndex(const int index) { mCtx.index = index;}
|
void setIndex(const int index) { mCtx.index = index;}
|
||||||
int getIndex() {return mCtx.index;}
|
int getIndex() const {return mCtx.index;}
|
||||||
|
|
||||||
void setGlobalReaderList(std::vector<ESMReader> *list) {mGlobalReaderList = list;}
|
// Assign parent esX files by tracking their indices in the global list of
|
||||||
std::vector<ESMReader> *getGlobalReaderList() {return mGlobalReaderList;}
|
// all files/readers used by the engine. This is required for correct adjustRefNum() results
|
||||||
|
// as required for handling moved, deleted and edited CellRefs.
|
||||||
void addParentFileIndex(int index) { mCtx.parentFileIndices.push_back(index); }
|
/// @note Does not validate.
|
||||||
|
void resolveParentFileIndices(const std::vector<ESMReader>& files);
|
||||||
const std::vector<int>& getParentFileIndices() const { return mCtx.parentFileIndices; }
|
const std::vector<int>& getParentFileIndices() const { return mCtx.parentFileIndices; }
|
||||||
|
bool isValidParentFileIndex(int i) const { return i != getIndex(); }
|
||||||
|
|
||||||
/*************************************************************************
|
/*************************************************************************
|
||||||
*
|
*
|
||||||
|
@ -279,7 +281,6 @@ private:
|
||||||
|
|
||||||
Header mHeader;
|
Header mHeader;
|
||||||
|
|
||||||
std::vector<ESMReader> *mGlobalReaderList;
|
|
||||||
ToUTF8::Utf8Encoder* mEncoder;
|
ToUTF8::Utf8Encoder* mEncoder;
|
||||||
|
|
||||||
size_t mFileSize;
|
size_t mFileSize;
|
||||||
|
|
|
@ -214,7 +214,6 @@ namespace EsmLoader
|
||||||
ESM::ESMReader& reader = readers[i];
|
ESM::ESMReader& reader = readers[i];
|
||||||
reader.setEncoder(encoder);
|
reader.setEncoder(encoder);
|
||||||
reader.setIndex(static_cast<int>(i));
|
reader.setIndex(static_cast<int>(i));
|
||||||
reader.setGlobalReaderList(&readers);
|
|
||||||
reader.open(collection.getPath(file).string());
|
reader.open(collection.getPath(file).string());
|
||||||
|
|
||||||
loadEsm(query, readers[i], result);
|
loadEsm(query, readers[i], result);
|
||||||
|
|
Loading…
Reference in a new issue