From abe6904a9494d9acf52d5bc512e921f77b7dccef Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 19 Dec 2015 17:54:45 +1100 Subject: [PATCH] Minor performance gains on loading cell references. - Use integers where possible - Unfortunately many functions are simply duplicated for now, but over time the deprecated ones will be removed --- components/esm/cellref.cpp | 12 +++--- components/esm/defs.hpp | 4 +- components/esm/esmreader.cpp | 81 ++++++++++++++++++++++++++++++++++++ components/esm/esmreader.hpp | 20 +++++++++ 4 files changed, 110 insertions(+), 7 deletions(-) diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index b3fb410e3..2aa36867b 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -6,9 +6,9 @@ void ESM::RefNum::load (ESMReader& esm, bool wide) { if (wide) - esm.getHNT (*this, "FRMR", 8); + esm.getHNT (*this, SREC_FRMR, 8); else - esm.getHNT (mIndex, "FRMR"); + esm.getHNT (mIndex, SREC_FRMR); } void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const @@ -36,14 +36,14 @@ void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) // the following refs are part of a "temp refs" section. A temp ref is not being tracked by the moved references system. // Its only purpose is a performance optimization for "immovable" things. We don't need this, and it's problematic anyway, // because any item can theoretically be moved by a script. - if (esm.isNextSub ("NAM0")) + if (esm.isNextSub (SREC_NAM0)) esm.skipHSub(); blank(); mRefNum.load (esm, wideRefNum); - mRefID = esm.getHNString ("NAME"); + esm.getHNString (SREC_NAME, mRefID); } void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) @@ -180,7 +180,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool void ESM::CellRef::blank() { mRefNum.unset(); - mRefID.clear(); + mRefID.clear(); mScale = 1; mOwner.clear(); mGlobalVariable.clear(); @@ -196,7 +196,7 @@ void ESM::CellRef::blank() mTrap.clear(); mReferenceBlocked = -1; mTeleport = false; - + for (int i=0; i<3; ++i) { mDoorDest.pos[i] = 0; diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 9dc088596..853c63c06 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -128,7 +128,9 @@ enum RecNameInts enum SubRecNameInts { SREC_DELE = ESM::FourCC<'D','E','L','E'>::value, - SREC_NAME = ESM::FourCC<'N','A','M','E'>::value + SREC_NAME = ESM::FourCC<'N','A','M','E'>::value, + SREC_NAM0 = ESM::FourCC<'N','A','M','0'>::value, + SREC_FRMR = ESM::FourCC<'F','R','M','R'>::value }; } diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 400ead4e2..b4b44f876 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -109,6 +109,12 @@ std::string ESMReader::getHNString(const char* name) return getHString(); } +void ESMReader::getHNString(const int name, std::string& str) +{ + getSubNameIs(name); + getHString(str); +} + std::string ESMReader::getHString() { getSubHeader(); @@ -130,6 +136,28 @@ std::string ESMReader::getHString() return getString(mCtx.leftSub); } +void ESMReader::getHString(std::string& str) +{ + getSubHeader(); + + // Hack to make MultiMark.esp load. Zero-length strings do not + // occur in any of the official mods, but MultiMark makes use of + // them. For some reason, they break the rules, and contain a byte + // (value 0) even if the header says there is no data. If + // Morrowind accepts it, so should we. + if (mCtx.leftSub == 0) + { + // Skip the following zero byte + mCtx.leftRec--; + char c; + getExact(&c, 1); + str = ""; + return; + } + + getString(str, mCtx.leftSub); +} + void ESMReader::getHExact(void*p, int size) { getSubHeader(); @@ -159,6 +187,24 @@ void ESMReader::getSubNameIs(const char* name) + mCtx.subName.toString()); } +void ESMReader::getSubNameIs(const int name) +{ + getSubName(); + if (mCtx.subName != name) + { + unsigned char typeName[4]; + typeName[0] = name & 0xff; + typeName[1] = (name >> 8) & 0xff; + typeName[2] = (name >> 16) & 0xff; + typeName[3] = (name >> 24) & 0xff; + + std::string subName = std::string((char*)typeName, 4); + + fail("Expected subrecord " + subName + " but got " + + mCtx.subName.toString()); + } +} + bool ESMReader::isNextSub(const char* name) { if (!mCtx.leftRec) @@ -174,6 +220,21 @@ bool ESMReader::isNextSub(const char* name) return !mCtx.subCached; } +bool ESMReader::isNextSub(const int name) +{ + if (!mCtx.leftRec) + return false; + + getSubName(); + + // If the name didn't match, then mark the it as 'cached' so it's + // available for the next call to getSubName. + mCtx.subCached = (mCtx.subName != name); + + // If subCached is false, then subName == name. + return !mCtx.subCached; +} + bool ESMReader::peekNextSub(const char *name) { if (!mCtx.leftRec) @@ -347,6 +408,26 @@ std::string ESMReader::getString(int size) return std::string (ptr, size); } +void ESMReader::getString(std::string& str, int size) +{ + size_t s = size; + if (mBuffer.size() <= s) + // Add some extra padding to reduce the chance of having to resize again later. + mBuffer.resize(3*s); + + mBuffer[s] = 0; // And make sure the string is zero terminated + + char *ptr = &mBuffer[0]; + getExact(ptr, size); // read ESM data + + size = static_cast(strnlen(ptr, size)); + + if (mEncoder) + str = mEncoder->getUtf8(ptr, size); // Convert to UTF8 and return + else + str = std::string (ptr, size); +} + void ESMReader::fail(const std::string &msg) { using namespace std; diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 66ef98130..3a2f32640 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -103,6 +103,13 @@ public: getHT(x); } + template + void getHNT(X &x, const int name) + { + getSubNameIs(name); + getHT(x); + } + // Optional version of getHNT template void getHNOT(X &x, const char* name) @@ -121,6 +128,14 @@ public: getHT(x); } + template + void getHNT(X &x, const int name, int size) + { + assert(sizeof(X) == size); + getSubNameIs(name); + getHT(x); + } + template void getHNOT(X &x, const char* name, int size) { @@ -159,9 +174,11 @@ public: // Read a string with the given sub-record name std::string getHNString(const char* name); + void getHNString(const int name, std::string& str); // Read a string, including the sub-record header (but not the name) std::string getHString(); + void getHString(std::string& str); // Read the given number of bytes from a subrecord void getHExact(void*p, int size); @@ -177,6 +194,7 @@ public: // Get the next subrecord name and check if it matches the parameter void getSubNameIs(const char* name); + void getSubNameIs(const int name); /** Checks if the next sub record name matches the parameter. If it does, it is read into 'subName' just as if getSubName() was @@ -184,6 +202,7 @@ public: calls to getSubName(), isNextSub() and getSubNameIs(). */ bool isNextSub(const char* name); + bool isNextSub(const int name); bool peekNextSub(const char* name); @@ -256,6 +275,7 @@ public: // Read the next 'size' bytes and return them as a string. Converts // them from native encoding to UTF8 in the process. std::string getString(int size); + void getString(std::string& str, int size); void skip(int bytes) { mEsm->seek(mEsm->tell()+bytes); } uint64_t getOffset() { return mEsm->tell(); }