/********************************************************************** Filename : GFxASString.cpp Content : String manager implementation for Action Script Created : Movember 7, 2006 Authors : Michael Antonov Notes : Implements optimized GASString class, which acts as a hash key for strings allocated from GASStringManager. Copyright : (c) 1998-2006 Scaleform Corp. All Rights Reserved. Licensees may use this file in accordance with the valid Scaleform Commercial License Agreement provided with the software. This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR ANY PURPOSE. **********************************************************************/ #include "GFxASString.h" // Log for leak reports #include "GFxLog.h" #include "GFxPlayerStats.h" // ***** GASString implementation // Static hash function calculation routine UInt32 GASString::HashFunction(const char *pchar, UPInt length) { return (UInt32)GString::BernsteinHashFunctionCIS(pchar, length) & Flag_HashMask; } void GASString::Clear() { AssignNode(GetManager()->GetEmptyStringNode()); } // Returns length in characters. UInt GASString::GetLength() const { UInt length, size = GetSize(); // Optimize length accesses for non-UTF8 character strings. if (GetHashFlags() & Flag_LengthIsSize) return size; length = (UInt)GUTF8Util::GetLength(GetBuffer(), GetSize()); if (length == size) pNode->HashFlags |= Flag_LengthIsSize; return length; } UInt32 GASString::GetCharAt(UInt index) const { if (GetHashFlags() & Flag_LengthIsSize) { return (UInt32) operator [](index); } int i = (int) index; const char* buf = GetBuffer(); UInt32 c; do { c = GUTF8Util::DecodeNextChar(&buf); i--; if (c == 0) { // We've hit the end of the string; don't go further. GASSERT(i == 0); return c; } } while (i >= 0); return c; } // The rest of the functions here operate in UTF8. For example, GASString GASString::AppendChar(UInt32 ch) { char buff[8]; SPInt index = 0; GUTF8Util::EncodeChar(buff, &index, ch); // Assign us a concatenated node. return GASString(GetManager()->CreateStringNode(pNode->pData, pNode->Size, buff, index)); } /* void GASString::Remove(int posAt, int len) { GUNUSED2(posAt, len); GASSERT(0); } */ // Returns a GString that's a substring of this. // -start is the index of the first UTF8 character you want to include. // -end is the index one past the last UTF8 character you want to include. GASString GASString::Substring(int start, int end) const { GASSERT(start <= end); // Special case, always return empty string. if (start == end) return GetManager()->CreateEmptyString(); int index = 0; const char* p = GetBuffer(); const char* start_pointer = p; const char* end_pointer = p; for (;;) { if (index == start) { start_pointer = p; } UInt32 c = GUTF8Util::DecodeNextChar(&p); index++; if (index == end) { end_pointer = p; break; } if (c == 0) { //!AB if the string is shorter than "end" param - then just // return a whole string as a substring. if (index < end) { end_pointer = p; } break; } } if (end_pointer < start_pointer) { end_pointer = start_pointer; } return GetManager()->CreateString(start_pointer, (int) (end_pointer - start_pointer)); } // Case-converted strings. GASString GASString::ToUpper() const { GString str = GString(pNode->pData).ToUpper(); return GetManager()->CreateString(str, str.GetSize()); } GASString GASString::ToLower() const { ResolveLowercase(); return GASString(pNode->pLower ? pNode->pLower : GetManager()->GetEmptyStringNode()); } // *** Operations void GASString::operator = (const char* pstr) { AssignNode(GetManager()->CreateStringNode(pstr, strlen(pstr))); } void GASString::operator += (const char* pstr) { AssignNode(GetManager()->CreateStringNode(pNode->pData, pNode->Size, pstr, strlen(pstr))); } void GASString::operator += (const GASString& str) { AssignNode(GetManager()->CreateStringNode(pNode->pData, pNode->Size, str.pNode->pData, str.pNode->Size)); } GASString GASString::operator + (const char* pstr) const { return GASString(GetManager()->CreateStringNode(pNode->pData, pNode->Size, pstr, strlen(pstr))); } GASString GASString::operator + (const GASString& str) const { return GASString(GetManager()->CreateStringNode(pNode->pData, pNode->Size, str.pNode->pData, str.pNode->Size)); } int GASString::LocaleCompare_CaseCheck(const char* pstr, UPInt len, bool caseSensitive) const { if (len == GFC_MAX_UPINT) len = G_strlen(pstr); wchar_t buf1[250], buf2[250]; wchar_t *pwstra = NULL, *pwstrb = NULL; UPInt la = GetLength(); if (la >= sizeof(buf1)/sizeof(buf1[0])) pwstra = (wchar_t*)GALLOC(sizeof(wchar_t) * (la + 1), GStat_Default_Mem); else pwstra = buf1; if (len >= sizeof(buf2)/sizeof(buf2[0])) pwstrb = (wchar_t*)GALLOC(sizeof(wchar_t) * (len + 1), GStat_Default_Mem); else pwstrb = buf2; GUTF8Util::DecodeString(pwstra, ToCStr(), la); GUTF8Util::DecodeString(pwstrb, pstr, len); int res; if (caseSensitive) res = G_wcscoll(pwstra, pwstrb); else res = G_wcsicoll(pwstra, pwstrb); if (pwstra != buf1) GFREE(pwstra); if (pwstrb != buf2) GFREE(pwstrb); return res; } // ***** Node implementation // Resolves pLower to a valid value. void GASStringNode::ResolveLowercase_Impl() { GString str = GString(pData).ToLower(); GASStringManager* pmanager = GetManager(); GASStringNode* pnode = pmanager->CreateStringNode(str); if (pnode != pmanager->GetEmptyStringNode()) { pLower = pnode; if (pnode != this) pnode->AddRef(); } } // Frees the node whose refCount has reached 0. void GASStringNode::ReleaseNode() { GASSERT(RefCount == 0); // If we had a lowercase version, release its ref count. if ((pLower != this) && pLower) pLower->Release(); GASStringManager* pmanager = GetManager(); pmanager->StringSet.Remove(this); pmanager->FreeStringNode(this); } // ***** GASStringManager GASStringManager::GASStringManager(GMemoryHeap* pheap) : pHeap(pheap) { // Allocate initial data. pStringNodePages = 0; pFreeStringNodes = 0; pFreeTextBuffers = 0; pTextBufferPages = 0; // Empty data - refcount 1, so it is never released. // This node must me allocated with its GetManager() implementation relies on alignment. pEmptyStringNode = AllocStringNode(); if (pEmptyStringNode) { pEmptyStringNode->RefCount = 1; pEmptyStringNode->Size = 0; pEmptyStringNode->HashFlags = GASString::HashFunction("", 0) | GASString::Flag_ConstData | GASString::Flag_Builtin; pEmptyStringNode->pData = ""; pEmptyStringNode->pLower = pEmptyStringNode; } StringSet.Add(pEmptyStringNode); } GASStringManager::~GASStringManager() { // Release string nodes. GStringBuffer leakReport; UInt ileaks = 0; while(pStringNodePages) { StringNodePage* ppage = pStringNodePages; pStringNodePages = ppage->pNext; // Free text in all active GASString objects. Technically this is a bug, since // all GASString should have already died before GASStringManager is destroyed. // However, if ActionScript leaks due to circular references, this will at least // release all of the string content involved. // In the future, once we have GC this may be changed to throw asserts if // unreleased data / RefCount is detected at this point. for (UInt i=0; i< ppage->StringNodeCount; i++) { if (ppage->Nodes[i].pData && (&ppage->Nodes[i] != pEmptyStringNode)) { if (ileaks < 16) { leakReport += (ileaks > 0) ? ", '" : "'"; leakReport += ppage->Nodes[i].pData; leakReport += "'"; } ileaks++; if (!(ppage->Nodes[i].HashFlags & GASString::Flag_ConstData)) { FreeTextBuffer(const_cast(ppage->Nodes[i].pData), ppage->Nodes[i].Size); } } } GHEAP_FREE(pHeap, ppage); } // Free text pages. while(pTextBufferPages) { void *pmem = pTextBufferPages->pMem; pTextBufferPages = pTextBufferPages->pNext; GHEAP_FREE(pHeap, pmem); } if (ileaks && pLog.GetPtr()) { pLog->LogScriptError("ActionScript Memory leaks in movie '%s', including %d string nodes\n", FileName.ToCStr(), ileaks); pLog->LogScriptError("Leaked string content: %s\n", leakReport.ToCStr()); } } // Sets the log that will be used to report leaks during destructor. void GASStringManager::SetLeakReportLog(GFxLog *plog, const char *pfilename) { pLog = plog; FileName= pfilename ? pfilename : ""; } void GASStringManager::AllocateStringNodes() { StringNodePage* ppage = (StringNodePage*) GHEAP_MEMALIGN(pHeap, sizeof(StringNodePage), StringNodePage::StringNodePageAlign, GFxStatMV_ActionScript_Mem); if (ppage) { ppage->pNext = pStringNodePages; ppage->pManager = this; pStringNodePages = ppage; // Add nodes to free pool. for (UInt i=0; i < StringNodePage::StringNodeCount; i++) { GASStringNode *pnode = &ppage->Nodes[i]; // Clear data so that release detection fails on it. pnode->pData = 0; // Insert into free list. pnode->pNextAlloc = pFreeStringNodes; pFreeStringNodes = pnode; } } else { // Not enough memory for string nodes!! GASSERT(1); } } void GASStringManager::AllocateTextBuffers() { // Align data (some of our allocation routines add size counters // that may case this to be misaligned at a multiple of 4 instead). // Seems to make a difference on P4 chips. void* pp = GHEAP_ALLOC(pHeap, sizeof(TextPage) + 8, GFxStatMV_ActionScript_Mem); TextPage* ppage = (TextPage*) ((((UPInt)pp)+7) & ~7); if (ppage) { ppage->pMem = pp; ppage->pNext = pTextBufferPages; pTextBufferPages = ppage; // Add nodes to free pool. for (UInt i=0; i < TextPage::BuffCount; i++) { TextPage::Entry *pe = ppage->Entries + i; pe->pNextAlloc = pFreeTextBuffers; pFreeTextBuffers = pe; } } else { // Not enough memory for text entries!! GASSERT(1); } } // Get buffer for text (adds 0 to length). char* GASStringManager::AllocTextBuffer(UPInt length) { if (length < TextPage::BuffSize) { if (!pFreeTextBuffers) AllocateTextBuffers(); char *pbuff = 0; if (pFreeTextBuffers) { pbuff = pFreeTextBuffers->Buff; pFreeTextBuffers = pFreeTextBuffers->pNextAlloc; } return pbuff; } // If bigger then the size of built-in buffer entry, allocate separately. return (char*)GHEAP_ALLOC(pHeap, length + 1, GFxStatMV_ActionScript_Mem); } void GASStringManager::FreeTextBuffer(char* pbuffer, UPInt length) { // This is never called with null pbuffer. GASSERT(pbuffer); if (length < TextPage::BuffSize) { // delta should be 0, but just in case of a strange compiler... ptrdiff_t delta = &reinterpret_cast(0)->Buff[0] - (char*)0; TextPage::Entry* pentry = reinterpret_cast(pbuffer - delta); pentry->pNextAlloc = pFreeTextBuffers; pFreeTextBuffers = pentry; return; } GHEAP_FREE(pHeap, pbuffer); } char* GASStringManager::AllocTextBuffer(const char* pbuffer, UPInt length) { char *pbuff = AllocTextBuffer(length); if (pbuff) { memcpy(pbuff, pbuffer, length); pbuff[length] = 0; } return pbuff; } // *** String node management bool operator == (GASStringNode* pnode, const GASStringKey &str) { if (pnode->Size != str.Length) return 0; return strncmp(pnode->pData, str.pStr, str.Length) == 0; } GASStringNode* GASStringManager::CreateConstStringNode(const char* pstr, UPInt length, UInt32 stringFlags) { GASStringNode* pnode; GASStringKey key(pstr, GASString::HashFunction(pstr, length), length); if (StringSet.GetAlt(key, &pnode)) { // Must set flags, otherwise setting could be lost quietly for strings // that are already registered, such as built-ins. pnode->HashFlags |= stringFlags; return pnode; } if ((pnode = AllocStringNode()) != 0) { pnode->RefCount = 0; pnode->Size = (UInt)length; pnode->pData = pstr; pnode->HashFlags= (UInt32)(key.HashValue | GASString::Flag_ConstData | stringFlags); pnode->pLower = 0; StringSet.Add(pnode); return pnode; } // Return dummy string node. return pEmptyStringNode; } GASStringNode* GASStringManager::CreateStringNode(const char* pstr, UPInt length) { GASStringNode* pnode; GASStringKey key(pstr, GASString::HashFunction(pstr, length), length); if (StringSet.GetAlt(key, &pnode)) return pnode; if ((pnode = AllocStringNode()) != 0) { pnode->pData = AllocTextBuffer(pstr, length); if (pnode->pData) { pnode->RefCount = 0; pnode->Size = (UInt)length; pnode->HashFlags= (UInt32)key.HashValue; pnode->pLower = 0; StringSet.Add(pnode); return pnode; } else { FreeStringNode(pnode); } } return pEmptyStringNode; } GASStringNode* GASStringManager::CreateStringNode(const char* pstr) { if (!pstr) return pEmptyStringNode; return CreateStringNode(pstr, strlen(pstr)); } // Wide character; use special type of counting. GASStringNode* GASStringManager::CreateStringNode(const wchar_t* pwstr) { // GString strBuff; // GString::EncodeUTF8FromWChar(&strBuff, pwstr); GString strBuff; strBuff = pwstr; return CreateStringNode(strBuff.ToCStr(), strBuff.GetSize()); } // These functions also perform string concatenation. GASStringNode* GASStringManager::CreateStringNode(const char* pstr1, UPInt l1, const char* pstr2, UPInt l2) { GASStringNode* pnode; UPInt length = l1 + l2; char* pbuffer = AllocTextBuffer(length); if (pbuffer) { if (l1 > 0) memcpy(pbuffer, pstr1, l1); if (l2 > 0) memcpy(pbuffer + l1, pstr2, l2); pbuffer[length] = 0; GASStringKey key(pbuffer, GASString::HashFunction(pbuffer, length), length); // If already exists, no need for node. if (StringSet.GetAlt(key, &pnode)) { FreeTextBuffer(pbuffer, length); return pnode; } // Create node. if ((pnode = AllocStringNode()) != 0) { pnode->RefCount = 0; pnode->Size = (UInt)length; pnode->pData = pbuffer; pnode->HashFlags= (UInt32)key.HashValue; pnode->pLower = 0; StringSet.Add(pnode); return pnode; } else { FreeTextBuffer(pbuffer, length); } } return pEmptyStringNode; } GASStringNode* GASStringManager::CreateStringNode(const char* pstr1, UPInt l1, const char* pstr2, UPInt l2, const char* pstr3, UPInt l3) { GASStringNode* pnode; UPInt length = l1 + l2 + l3; char* pbuffer = AllocTextBuffer(length); if (pbuffer) { if (l1 > 0) memcpy(pbuffer, pstr1, l1); if (l2 > 0) memcpy(pbuffer + l1, pstr2, l2); if (l3 > 0) memcpy(pbuffer + l1 + l2, pstr3, l3); pbuffer[length] = 0; GASStringKey key(pbuffer, GASString::HashFunction(pbuffer, length), length); // If already exists, no need for node. if (StringSet.GetAlt(key, &pnode)) { FreeTextBuffer(pbuffer, length); return pnode; } // Create node. if ((pnode = AllocStringNode()) != 0) { pnode->RefCount = 0; pnode->Size = (UInt)length; pnode->pData = pbuffer; pnode->HashFlags= (UInt32)key.HashValue; pnode->pLower = 0; StringSet.Add(pnode); return pnode; } else { FreeTextBuffer(pbuffer, length); } } return pEmptyStringNode; }