443 lines
13 KiB
C++
Executable File
443 lines
13 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "core/stringBuffer.h"
|
|
#include "core/frameAllocator.h"
|
|
#include "core/unicode.h"
|
|
|
|
|
|
#if defined(TORQUE_DEBUG)
|
|
class StringBufferManager
|
|
{
|
|
public:
|
|
static StringBufferManager& getManager();
|
|
Vector<StringBuffer*> strings;
|
|
U64 request8;
|
|
U64 request16;
|
|
|
|
void add(StringBuffer* s);
|
|
void remove(StringBuffer* s);
|
|
void updateStats();
|
|
void dumpStats();
|
|
void dumpAllStrings();
|
|
};
|
|
|
|
ConsoleFunction(sbmDumpStats, void, 1, 1, "")
|
|
{
|
|
StringBufferManager::getManager().dumpStats();
|
|
}
|
|
|
|
ConsoleFunction(sbmDumpStrings, void, 1, 1, "")
|
|
{
|
|
StringBufferManager::getManager().dumpAllStrings();
|
|
}
|
|
#endif // TORQUE_DEBUG
|
|
|
|
|
|
#if defined(TORQUE_DEBUG)
|
|
#define SBMAddThisStringBuffer() \
|
|
StringBufferManager::getManager().add(this); \
|
|
rc = new RequestCounts; \
|
|
clearRequestCounts()
|
|
#define incRequestCount8() rc->requestCount8++
|
|
#define incRequestCount16() rc->requestCount16++
|
|
#define decRequestCount16() rc->requestCount16--
|
|
#else
|
|
#define SBMAddThisStringBuffer()
|
|
#define incRequestCount8()
|
|
#define incRequestCount16()
|
|
#define decRequestCount16()
|
|
#endif
|
|
|
|
StringBuffer::StringBuffer() : mBuffer(), mBuffer8(), mDirty8(true)
|
|
{
|
|
SBMAddThisStringBuffer();
|
|
mBuffer.push_back(0);
|
|
};
|
|
|
|
/// Copy constructor. Very important.
|
|
StringBuffer::StringBuffer(const StringBuffer ©) : mBuffer(), mBuffer8()
|
|
{
|
|
SBMAddThisStringBuffer();
|
|
set(©);
|
|
};
|
|
|
|
StringBuffer::StringBuffer(const StringBuffer *in)
|
|
: mBuffer(), mBuffer8()
|
|
{
|
|
SBMAddThisStringBuffer();
|
|
set(in);
|
|
}
|
|
|
|
StringBuffer::StringBuffer(const UTF8 *in)
|
|
: mBuffer(), mBuffer8()
|
|
{
|
|
SBMAddThisStringBuffer();
|
|
set(in);
|
|
}
|
|
|
|
StringBuffer::StringBuffer(const UTF16 *in)
|
|
: mBuffer(), mBuffer8()
|
|
{
|
|
SBMAddThisStringBuffer();
|
|
set(in);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
StringBuffer::~StringBuffer()
|
|
{
|
|
// Everything will get cleared up nicely cuz it's a vector. Sweet.
|
|
#if defined(TORQUE_DEBUG)
|
|
StringBufferManager::getManager().remove(this);
|
|
delete rc;
|
|
#endif
|
|
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
void StringBuffer::set(const StringBuffer *in)
|
|
{
|
|
// Copy the vector.
|
|
mBuffer.setSize(in->length()+1);
|
|
dMemcpy(mBuffer.address(), in->mBuffer.address(), sizeof(UTF16)*in->mBuffer.size());
|
|
mDirty8 = true;
|
|
}
|
|
|
|
void StringBuffer::set(const UTF8 *in)
|
|
{
|
|
incRequestCount8();
|
|
// Convert and store. Note that a UTF16 version of the string cannot be longer.
|
|
FrameTemp<UTF16> tmpBuff(dStrlen(in)+1);
|
|
if(!in || in[0] == 0 || !convertUTF8toUTF16(in, tmpBuff, dStrlen(in)+1))
|
|
{
|
|
// Easy out, it's a blank string, or a bad string.
|
|
mBuffer.clear();
|
|
mBuffer.push_back(0);
|
|
AssertFatal(mBuffer.last() == 0, "StringBuffer::set UTF8 - not a null terminated string!");
|
|
return;
|
|
}
|
|
|
|
// Otherwise, we've a copy to do. (This might not be strictly necessary.)
|
|
mBuffer.setSize(dStrlen(tmpBuff)+1);
|
|
dMemcpy(mBuffer.address(), tmpBuff, sizeof(UTF16) * mBuffer.size());
|
|
mBuffer.compact();
|
|
AssertFatal(mBuffer.last() == 0, "StringBuffer::set UTF8 - not a null terminated string!");
|
|
mDirty8 = true;
|
|
}
|
|
|
|
void StringBuffer::set(const UTF16 *in)
|
|
{
|
|
incRequestCount16();
|
|
// Just copy, it's already UTF16.
|
|
mBuffer.setSize(dStrlen(in)+1);
|
|
dMemcpy(mBuffer.address(), in, sizeof(UTF16) * mBuffer.size());
|
|
mBuffer.compact();
|
|
AssertFatal(mBuffer.last() == 0, "StringBuffer::set UTF16 - not a null terminated string!");
|
|
mDirty8 = true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
void StringBuffer::append(const StringBuffer &in)
|
|
{
|
|
append(in.mBuffer.address(), in.length());
|
|
}
|
|
|
|
void StringBuffer::append(const UTF8* in)
|
|
{
|
|
incRequestCount8();
|
|
decRequestCount16(); // because we're about to inc it when we go through append(utf16)
|
|
|
|
// convert to UTF16, because that's our internal format.
|
|
// if the conversion fails, exit.
|
|
UTF16* tmp = convertUTF8toUTF16(in);
|
|
AssertFatal(tmp, "StringBuffer::append(UTF8) - could not convert UTF8 string!");
|
|
if(!tmp)
|
|
return;
|
|
|
|
append(tmp);
|
|
delete [] tmp;
|
|
}
|
|
|
|
void StringBuffer::append(const UTF16* in)
|
|
{
|
|
AssertFatal(in, "StringBuffer::append(UTF16) - null UTF16 string!");
|
|
append(in, dStrlen(in));
|
|
}
|
|
|
|
void StringBuffer::append(const UTF16* in, const U32 len)
|
|
{
|
|
incRequestCount16();
|
|
|
|
// Stick in onto the end of us - first make space.
|
|
U32 oldSize = length();
|
|
mBuffer.increment(len);
|
|
|
|
// Copy it in, ignoring both our terminator and theirs.
|
|
dMemcpy(&mBuffer[oldSize], in, sizeof(UTF16) * len);
|
|
|
|
// Terminate the string.
|
|
mBuffer.last() = 0;
|
|
|
|
// mark utf8 buffer dirty
|
|
mDirty8 = true;
|
|
}
|
|
|
|
void StringBuffer::insert(const U32 charOffset, const StringBuffer &in)
|
|
{
|
|
insert(charOffset, in.mBuffer.address(), in.length());
|
|
}
|
|
|
|
void StringBuffer::insert(const U32 charOffset, const UTF8* in)
|
|
{
|
|
incRequestCount8();
|
|
decRequestCount16();
|
|
|
|
// convert to UTF16, because that's our internal format.
|
|
// if the conversion fails, exit.
|
|
UTF16* tmp = convertUTF8toUTF16(in);
|
|
AssertFatal(tmp, "StringBuffer::insert(UTF8) - could not convert UTF8 string!");
|
|
if(!tmp)
|
|
return;
|
|
|
|
insert(charOffset, tmp);
|
|
delete [] tmp;
|
|
}
|
|
|
|
void StringBuffer::insert(const U32 charOffset, const UTF16* in)
|
|
{
|
|
AssertFatal(in, "StringBuffer::insert(UTF16) - null UTF16 string!");
|
|
insert(charOffset, in, dStrlen(in));
|
|
}
|
|
|
|
void StringBuffer::insert(const U32 charOffset, const UTF16* in, const U32 len)
|
|
{
|
|
incRequestCount16();
|
|
|
|
// Deal with append case.
|
|
if(charOffset >= length())
|
|
{
|
|
append(in, len);
|
|
return;
|
|
}
|
|
|
|
// Append was easy, now we have to do some work.
|
|
|
|
// Copy everything we have that comes after charOffset past where the new
|
|
// string data will be.
|
|
|
|
// Figure the address to start copying at. We know this is ok as otherwise
|
|
// we'd be in the append case.
|
|
const UTF16 *copyStart = &mBuffer[charOffset];
|
|
|
|
// Figure the number of UTF16's to copy, taking into account the possibility
|
|
// that we may be inserting a long string into a short string.
|
|
|
|
// How many chars come after the insert point? Add 1 to deal with terminator.
|
|
const U32 copyCharLength = (S32)(length() - charOffset) + 1;
|
|
|
|
// Make some space in our buffer. We only need in.length() more as we
|
|
// will be dropping one of the two terminators present in this operation.
|
|
mBuffer.increment(len);
|
|
|
|
for(S32 i=copyCharLength-1; i>=0; i--)
|
|
mBuffer[charOffset+i+len] = mBuffer[charOffset+i];
|
|
|
|
// Can't copy directly: memcpy behavior is undefined if src and dst overlap.
|
|
//dMemcpy(&mBuffer[charOffset+len], &mBuffer[charOffset], sizeof(UTF16) * copyCharLength);
|
|
|
|
// And finally copy the new data in, not including its terminator.
|
|
dMemcpy(&mBuffer[charOffset], in, sizeof(UTF16) * len);
|
|
|
|
// All done!
|
|
AssertFatal(mBuffer.last() == 0, "StringBuffer::insert - not a null terminated string!");
|
|
|
|
// mark utf8 buffer dirty
|
|
mDirty8 = true;
|
|
}
|
|
|
|
StringBuffer StringBuffer::substring(const U32 start, const U32 len) const
|
|
{
|
|
// Deal with bonehead user input.
|
|
if(start >= length() || len == 0)
|
|
{
|
|
// Either they asked beyond the end of the string or we're null. Either
|
|
// way, let's give them a null string.
|
|
return StringBuffer("");
|
|
}
|
|
|
|
AssertFatal(start < length(), "StringBuffer::substring - invalid start!");
|
|
AssertFatal(start+len <= length(), "StringBuffer::substring - invalid len!");
|
|
AssertFatal(len > 0, "StringBuffer::substring - len must be >= 1.");
|
|
|
|
StringBuffer tmp;
|
|
tmp.mBuffer.clear();
|
|
for(S32 i=0; i<len; i++)
|
|
tmp.mBuffer.push_back(mBuffer[start+i]);
|
|
if(tmp.mBuffer.last() != 0) tmp.mBuffer.push_back(0);
|
|
|
|
// Make sure this shit is terminated; we might get a totally empty string.
|
|
if(!tmp.mBuffer.size())
|
|
tmp.mBuffer.push_back(0);
|
|
|
|
return tmp;
|
|
}
|
|
|
|
UTF8* StringBuffer::createSubstring8(const U32 start, const U32 len) const
|
|
{
|
|
incRequestCount8();
|
|
|
|
StringBuffer sub = this->substring(start, len);
|
|
return sub.createCopy8();
|
|
}
|
|
|
|
void StringBuffer::cut(const U32 start, const U32 len)
|
|
{
|
|
AssertFatal(start < length(), "StringBuffer::cut - invalid start!");
|
|
AssertFatal(start+len <= length(), "StringBuffer::cut - invalid len!");
|
|
AssertFatal(len > 0, "StringBuffer::cut - len >= 1.");
|
|
|
|
AssertFatal(mBuffer.last() == 0, "StringBuffer::cut - not a null terminated string! (pre)");
|
|
|
|
// Now snip things.
|
|
for(S32 i=start; i<mBuffer.size()-len; i++)
|
|
mBuffer[i] = mBuffer[i+len];
|
|
mBuffer.decrement(len);
|
|
mBuffer.compact();
|
|
|
|
AssertFatal(mBuffer.last() == 0, "StringBuffer::cut - not a null terminated string! (post)");
|
|
|
|
// mark utf8 buffer dirty
|
|
mDirty8 = true;
|
|
}
|
|
|
|
const UTF16 StringBuffer::getChar(const U32 offset) const
|
|
{
|
|
incRequestCount16();
|
|
|
|
// Allow them to grab the null terminator if they want.
|
|
AssertFatal(offset<mBuffer.size(), "StringBuffer::getChar - outside of range.");
|
|
|
|
return mBuffer[offset];
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
void StringBuffer::getCopy8(UTF8 *buff, const U32 buffSize) const
|
|
{
|
|
incRequestCount8();
|
|
AssertFatal(mBuffer.last() == 0, "StringBuffer::get UTF8 - not a null terminated string!");
|
|
convertUTF16toUTF8(mBuffer.address(), buff, buffSize);
|
|
}
|
|
|
|
void StringBuffer::getCopy(UTF16 *buff, const U32 buffSize) const
|
|
{
|
|
incRequestCount16();
|
|
// Just copy it out.
|
|
AssertFatal(mBuffer.last() == 0, "StringBuffer::get UTF8 - not a null terminated string!");
|
|
dMemcpy(buff, mBuffer.address(), buffSize*sizeof(UTF16));
|
|
// ensure null termination.
|
|
buff[buffSize-1] = NULL;
|
|
}
|
|
|
|
UTF8* StringBuffer::createCopy8() const
|
|
{
|
|
incRequestCount8();
|
|
// convert will create a buffer of the appropriate size for a null terminated
|
|
// input string.
|
|
return convertUTF16toUTF8(mBuffer.address());
|
|
}
|
|
|
|
const UTF16* StringBuffer::getPtr() const
|
|
{
|
|
incRequestCount16();
|
|
// get a pointer to the StringBuffer's data store.
|
|
// this pointer is volatile, and not thread safe.
|
|
// use this in situations where you can be sure that the StringBuffer will
|
|
// not be modified out from under you.
|
|
// the key here is, you avoid yet another data copy. data copy is slow on
|
|
// most modern hardware.
|
|
return mBuffer.address();
|
|
}
|
|
|
|
const UTF8* StringBuffer::getPtr8() const
|
|
{
|
|
incRequestCount8();
|
|
// get a pointer to the utf8 version of the StringBuffer's data store.
|
|
// if the utf8 version is dirty, update it first.
|
|
if(mDirty8)
|
|
const_cast<StringBuffer *>(this)->updateBuffer8();
|
|
return mBuffer8.address();
|
|
}
|
|
|
|
void StringBuffer::updateBuffer8()
|
|
{
|
|
U32 slackLen = getUTF8BufferSizeEstimate();
|
|
mBuffer8.setSize(slackLen);
|
|
U32 len = convertUTF16toUTF8(mBuffer.address(), mBuffer8.address(), slackLen);
|
|
mBuffer8.setSize(len+1);
|
|
mBuffer8.compact();
|
|
mDirty8 = false;
|
|
}
|
|
|
|
|
|
#if defined(TORQUE_DEBUG)
|
|
StringBufferManager& StringBufferManager::getManager()
|
|
{
|
|
static StringBufferManager _sbm; return _sbm;
|
|
}
|
|
|
|
void StringBufferManager::add(StringBuffer* s)
|
|
{
|
|
strings.push_back(s);
|
|
}
|
|
|
|
void StringBufferManager::remove(StringBuffer* s)
|
|
{
|
|
U32 nstrings = strings.size();
|
|
for(int i=0; i < nstrings; i++)
|
|
if(strings[i] == s)
|
|
strings.erase_fast(i);
|
|
}
|
|
|
|
void StringBufferManager::updateStats()
|
|
{
|
|
request8 = 0;
|
|
request16 = 0;
|
|
U32 nstrings = strings.size();
|
|
for(int i=0; i < nstrings; i++)
|
|
{
|
|
request8 += strings[i]->rc->requestCount8;
|
|
request16 += strings[i]->rc->requestCount16;
|
|
}
|
|
}
|
|
|
|
void StringBufferManager::dumpStats()
|
|
{
|
|
updateStats();
|
|
Con::printf("===== String Manager Stats =====");
|
|
Con::printf(" strings: %i", strings.size());
|
|
Con::printf(" utf8 requests: %Lu", request8);
|
|
Con::printf(" utf16 requests: %Lu", request16);
|
|
}
|
|
|
|
void StringBufferManager::dumpAllStrings()
|
|
{
|
|
U32 nstrings = strings.size();
|
|
Con::printf("===== String Manager: All Strings =====");
|
|
Con::printf(" utf8 | utf16 | string");
|
|
for(int i=0; i < nstrings; i++)
|
|
{
|
|
UTF8* tmp = strings[i]->createCopy8();
|
|
strings[i]->rc->requestCount8--;
|
|
Con::printf("%5llu %5llu \"%s\"", strings[i]->rc->requestCount8, strings[i]->rc->requestCount16, tmp);
|
|
delete[] tmp;
|
|
}
|
|
}
|
|
|
|
#endif
|