#pragma once #include "Allocators.hpp" #include "StringLogic.hpp" namespace tp { // Static data extern ModuleManifest gModuleStrings; template class StringTemplate; // Hidden slave class template class StringData { friend StringTemplate; typedef StringLogic Logic; typedef typename StringLogic::Index Index; private: uint2 mIsConst; // source is non-modifiable uint2 mEditedIdx; // editor id uint4 mReferenceCount; // number of users of this tChar* mBuff; // actual string data StringData() { mBuff = (tChar*) "*"; mReferenceCount = 1; mIsConst = true; mEditedIdx = 0; } StringData(const tChar* aBuff, bool aIsConst) { MODULE_SANITY_CHECK(gModuleStrings) DEBUG_ASSERT(aBuff) mBuff = (tChar*) aBuff; mReferenceCount = 0; mIsConst = aIsConst; mEditedIdx = 0; } explicit StringData(tChar* aBuff) { MODULE_SANITY_CHECK(gModuleStrings) DEBUG_ASSERT(aBuff) auto const len = Logic::calcLength(aBuff); mReferenceCount = 0; mIsConst = false; mEditedIdx = 0; mBuff = new tChar[len + 1]; mBuff[len] = Logic::getEndChar(); memCopy(mBuff, aBuff, len); } [[nodiscard]] tChar* getBuffer() { return mBuff; } ~StringData() { if (!mIsConst) delete[] mBuff; } void resize(Index size) { DEBUG_ASSERT(mReferenceCount == 1 && !mIsConst) delete[] mBuff; mBuff = new tChar[size + 1]; mBuff[size] = Logic::getEndChar(); } }; template class StringTemplate { public: typedef StringLogic Logic; typedef StringData Data; typedef typename Logic::Index Index; enum { STRINGS_POOL_SIZE = 1024, MAX_INT_STRING_LENGTH = 10, MAX_BOOL_STRING_LENGTH = 10, MAX_FLOAT_STRING_LENGTH = 10, }; static PoolAlloc, STRINGS_POOL_SIZE> sStringPool; static Data sNullString; private: Data* mData; public: // Main Interface // Creates null string reference StringTemplate() { mData = &sNullString; incReference(mData); } // References existing string data StringTemplate(const StringTemplate& in) { mData = in.mData; incReference(mData); } // Creates new string data from raw data pointer. // Does not own string buffer - string buffer will not be freed. Initializes as const memory. StringTemplate(const tChar* raw) { mData = new (sStringPool.allocate(0)) StringData(raw, true); incReference(mData); } // Copies raw data and claims ownership over it StringTemplate(tChar* raw) { mData = new (sStringPool.allocate(0)) StringData(raw); incReference(mData); } ~StringTemplate() { decReference(mData); } private: static void incReference(Data* dp) { dp->mReferenceCount++; } void decReference(Data* dp) { DEBUG_ASSERT(dp->mReferenceCount > 0) dp->mReferenceCount--; if (!dp->mReferenceCount) { mData->~StringData(); sStringPool.deallocate(mData); } } public: // Access // used to read data [[nodiscard]] const tChar* read() const { return mData->mBuff; } // used to write data // output will be null if in TextEditing mode [[nodiscard]] tChar* write() { makeModifiable(); return mData->getBuffer(); } // output will be null if in TextEditing mode tChar* resize(Index size) { makeModifiable(); mData->resize(size); return write(); } public: void makeModifiable() { // We have no rights to modify if someone references that memory too // So we need to create new copy of StringData if (mData->mReferenceCount > 1) { mData = new (sStringPool.allocate(0)) StringData(mData->mBuff); incReference(mData); return; } // Also if initial raw data was marked as const - create copy of raw data // Note - raw data will be lost at this point if no other record of such is stored if (mData->mIsConst) { mData = new (sStringPool.allocate(0)) StringData(mData->mBuff); incReference(mData); } } template void archiveWrite(tArchiver& ar) const { auto size = this->size(); ar << size; ar.writeBytes(read(), size); } template void archiveRead(tArchiver& ar) { Index size; ar >> size; resize(size); ar.readBytes(write(), size); } public: // Syntax sugars explicit StringTemplate(alni val) { tChar raw[MAX_INT_STRING_LENGTH]; Logic::convertValueToString(val, raw, MAX_INT_STRING_LENGTH); mData = new (sStringPool.allocate(0)) StringData(raw); incReference(mData); } explicit StringTemplate(alnf val) { tChar raw[MAX_INT_STRING_LENGTH]; Logic::convertValueToString(val, raw, MAX_FLOAT_STRING_LENGTH); mData = new (sStringPool.allocate(0)) StringData(raw); incReference(mData); } explicit StringTemplate(bool val) { tChar raw[MAX_INT_STRING_LENGTH]; Logic::convertValueToString(val, raw, MAX_BOOL_STRING_LENGTH); mData = new (sStringPool.allocate(0)) StringData(raw); incReference(mData); } explicit operator alni() { alni out; Logic::convertStringToValue(read(), out); return out; } explicit operator alnf() { alnf out; Logic::convertStringToValue(read(), out); return out; } explicit operator bool() { bool out; Logic::convertStringToValue(read(), out); return out; } Index size() const { return Logic::calcLength(read()); } void calcLineOffsets(Buffer& aOut) const { Logic::calcLineOffsets(read(), size(), aOut); } StringTemplate& operator=(tChar* in) { this->~StringTemplate(); new (this) StringTemplate(in); return *this; } StringTemplate& operator=(const tChar* in) { this->~StringTemplate(); new (this) StringTemplate(in); return *this; } StringTemplate& operator=(const StringTemplate& in) { if (&in == this) return *this; this->~StringTemplate(); new (this) StringTemplate(in); return *this; } bool operator==(const StringTemplate& in) const { if (&in == this) return true; if (in.mData == mData) return true; return Logic::isEqualLogic(in.read(), read()); } StringTemplate operator+(const StringTemplate& in) const { StringTemplate out; auto const newSize = in.size() + size(); out.resize(newSize); Logic::insertLogic(out.write(), newSize, read(), in.read(), size(), in.size()); return out; } public: // Debugging [[nodiscard]] ualni getReferenceCount() const { return mData->mReferenceCount; } [[nodiscard]] bool getIsConstFlag() const { return mData->mIsConst; } }; template PoolAlloc, StringTemplate::STRINGS_POOL_SIZE> StringTemplate::sStringPool; template StringData StringTemplate::sNullString; using String = StringTemplate; template ualni hash(StringTemplate in) { return hash(in.read()); } }