Files
GTASource/game/text/PagedText.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

334 lines
8.1 KiB
C++

//
// PagedText.cpp
//
// Copyright (C) 2012 Rockstar Games. All Rights Reserved.
//
// rage
#include "parser/manager.h"
#include "parser/macros.h"
#include "parser/treenode.h"
#include "fwnet/netchannel.h"
#include "fwmaths/random.h"
//framework
#include "streaming/streamingengine.h"
// game
#include "Text/PagedText.h"
#include "frontend/Scaleform/ScaleFormMgr.h"
NETWORK_OPTIMISATIONS()
RAGE_DECLARE_SUBCHANNEL(net, sc)
#undef __net_channel
#define __net_channel net_sc
// we need to use a smaller size to make room for any HTML conversions.
#define MAX_PAGE_TEXT_SIZE (MAX_CHARS_FOR_TEXT_STRING - 70)
#define PREALLOCATE_POLICY_BUFFER (85*1024)
#define MAX_PAGE_SIZE (45*1024)
static const char* sCloudTextTag = "Text";
bank_u32 PagedCloudText::sm_maxAttempts = 3;
bank_s32 PagedCloudText::sm_maxTimeoutRetryDelay = 12000;
bank_s32 PagedCloudText::sm_timeoutRetryDelayVariance = 3000;
void PagedText::Reset()
{
m_numPages=0;
for(int i=0; i<MAX_PAGES_OF_TEXT; ++i)
{
m_textPages[i].Clear();
}
}
void PagedText::ParseRawText(const char* pText, int textLen)
{
Reset();
if(gnetVerify(pText) && gnetVerify(textLen))
{
gnetDisplay("PagedText::ParseRawText - Parsing Text from cloud, length is %d characters", textLen);
gnetAssert(m_numPages == 0);
m_textPages[m_numPages].Preallocate((unsigned)MIN(textLen, MAX_PAGE_SIZE));
u32 doubleNewIndex = textLen;
u32 doubleNewLen = 0;
FindNextDoubleSpace(pText, textLen, doubleNewIndex, doubleNewLen);
for (u32 i = 0; i<textLen; ++i)
{
if (pText[i+0] == '[' &&
i+5<textLen &&
pText[i+1] == 'P' &&
pText[i+2] == 'A' &&
pText[i+3] == 'G' &&
pText[i+4] == 'E' &&
pText[i+5] == ']' )
{
i += 5;
++m_numPages;
unsigned preAllocSize = MIN((textLen-i), MAX_PAGE_SIZE);
gnetDisplay("PagedText::ParseRawText - New Page found at index %d, textLen=%d, prealloc=%d", i, textLen, preAllocSize);
m_textPages[m_numPages].Preallocate(preAllocSize);
continue;
}
if(i == doubleNewIndex)
{
i += doubleNewLen - 1;
FindNextDoubleSpace(pText+i, textLen, doubleNewIndex, doubleNewLen);
doubleNewIndex += i;
m_textPages[m_numPages].Append("\r\n");
continue;
}
if(pText[i] != '\0') // Let's not add an extra \0 at the end of the string, it just wastes memory (and asserts)
{
m_textPages[m_numPages].Append(pText[i]);
}
}
++m_numPages;
#if !__NO_OUTPUT
gnetDisplay("PagedText::ParseRawText - %d pages parsed.", Size());
for(int i=0; i<Size(); ++i)
{
gnetDisplay("PagedText::ParseRawText - Page %d length is %d characters", i, m_textPages[i].Length());
}
#endif
}
}
void PagedText::FindNextDoubleSpace(const char* pText, int textLen, u32& outIndex, u32& outLen)
{
const char* doubleNews[] = {
"\r\n\r\n",
"\r\n \r\n",
};
outIndex = textLen+1;
outLen = 0;
for(int i=0; i<NELEM(doubleNews); ++i)
{
const char* pDoubleNew = strstr(pText, doubleNews[i]);
u32 doubleNewIndex = (u32)(pDoubleNew ? (pDoubleNew - pText) : (textLen+1));
if(doubleNewIndex < outIndex)
{
outIndex = doubleNewIndex;
outLen = (u32)strlen(doubleNews[i]);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
PagedCloudText::PagedCloudText()
{
m_fileRequestId = INVALID_CLOUD_REQUEST_ID;
Shutdown();
}
void PagedCloudText::Init(const atString& filename, bool fromCloud BANK_ONLY(, u8 debugForceAttemptTimeouts))
{
m_filename = filename;
m_retryTimer.Zero();
if(!fromCloud)
{
m_filename = "platform:/data/";
m_filename += filename;
gnetDisplay("PagedCloudText::Init - Going to Parse the local version of %s", m_filename.c_str());
ParseFile(m_filename.c_str());
if(!m_didSucceed)
{
gnetDisplay("PagedCloudText::Init - ParseFile of local version failed for %s, will try to download from the cloud", m_filename.c_str());
}
m_isLocalFile = m_didSucceed;
}
if(!m_didSucceed)
{
m_filename = filename;
}
BANK_ONLY(m_debugForceAttemptTimeouts = debugForceAttemptTimeouts;)
}
void PagedCloudText::Shutdown()
{
m_filename.Reset();
m_pagedText.Reset();
Reset();
}
void PagedCloudText::Reset()
{
m_attempts = 0;
m_resultCode = 0;
m_didSucceed = false;
m_isLocalFile = false;
m_fileRequestId = INVALID_CLOUD_REQUEST_ID;
m_retryTimer.Zero();
BANK_ONLY(m_debugForceAttemptTimeouts = 0;)
}
void PagedCloudText::Update()
{
if(m_fileRequestId == INVALID_CLOUD_REQUEST_ID &&
m_resultCode == 0 &&
!m_didSucceed &&
m_filename.length() > 0 &&
GetNumPages() == 0 &&
m_retryTimer.IsComplete(sm_maxTimeoutRetryDelay, false))
{
StartDownload();
}
}
void PagedCloudText::StartDownload()
{
gnetDisplay("PagedCloudText::StartDownload - Starting download of %s", m_filename.c_str());
// Not caching since it causes frame hitches, and the player should only enter this menu once.
m_fileRequestId = CloudManager::GetInstance().RequestGetTitleFile(m_filename.c_str(), PREALLOCATE_POLICY_BUFFER, eRequest_CacheNone);
m_retryTimer.Zero();
}
void PagedCloudText::OnCloudEvent(const CloudEvent* pEvent)
{
switch(pEvent->GetType())
{
case CloudEvent::EVENT_REQUEST_FINISHED:
{
// grab event data
const CloudEvent::sRequestFinishedEvent* pEventData = pEvent->GetRequestFinishedData();
// check if we're interested
if(pEventData->nRequestID != m_fileRequestId)
return;
m_didSucceed = pEventData->bDidSucceed;
m_resultCode = pEventData->nResultCode;
m_fileRequestId = INVALID_CLOUD_REQUEST_ID;
#if __BANK
if(m_attempts < m_debugForceAttemptTimeouts)
{
m_didSucceed = false;
m_resultCode = NET_HTTPSTATUS_REQUEST_TIMEOUT;
}
#endif
// did we get the file...
if(m_didSucceed)
{
gnetDisplay("PagedCloudText::OnCloudEvent - Download Success - %s", m_filename.c_str());
char bufferFName[RAGE_MAX_PATH];
fiDevice::MakeMemoryFileName(bufferFName, sizeof(bufferFName), pEventData->pData, pEventData->nDataSize, false, m_filename.c_str());
ParseFile(bufferFName);
if(!m_didSucceed)
{
gnetDisplay("PagedCloudText::OnCloudEvent - Parsing failed, marking as 404 - %s", m_filename.c_str());
m_resultCode = NET_HTTPSTATUS_NOT_FOUND;
}
}
else if(IsTimeoutError(static_cast<netHttpStatusCodes>(m_resultCode)) && m_attempts < sm_maxAttempts)
{
gnetDisplay("PagedCloudText::OnCloudEvent - Download Timeout - %s", m_filename.c_str());
++m_attempts;
m_resultCode = 0;
m_retryTimer.Start(fwRandom::GetRandomNumberInRange(0, sm_timeoutRetryDelayVariance));
}
else
{
gnetDisplay("PagedCloudText::OnCloudEvent - Download Failed - %s, resultCode=%d", m_filename.c_str(), m_resultCode);
// We give precedence to the cloud version but if the download fails we'll then
// fall back to the local version.
// The use will see the accept-screen with the on-disk text and he has to accept it.
// The next time he goes online again he'll have to re-download and re-accept the legals
// if the local text had an older version.
atString localName = m_filename;
Init(localName, false BANK_ONLY(, m_debugForceAttemptTimeouts));
if (m_didSucceed)
{
gnetDisplay("PagedCloudText::OnCloudEvent - Reverting to local file - %s. This file might have a different version number than the server.", m_filename.c_str());
}
}
}
break;
case CloudEvent::EVENT_AVAILABILITY_CHANGED:
break;
};
}
void PagedCloudText::ParseFile(const char* pFilename)
{
m_didSucceed = false;
//Now load the text file.
INIT_PARSER;
{
parTree* pDataXML = PARSER.LoadTree(pFilename, "");
if(pDataXML)
{
const parTreeNode* pRootNode = pDataXML->GetRoot();
if(gnetVerify(pRootNode))
{
const parTreeNode* pTextNode = pRootNode->FindChildWithName(sCloudTextTag);
if(gnetVerify(pTextNode))
{
m_pagedText.ParseRawText(pTextNode->GetData(), pTextNode->GetDataSize());
m_didSucceed = true;
}
}
}
delete pDataXML;
}
SHUTDOWN_PARSER;
}
bool PagedCloudText::IsTimeoutError(netHttpStatusCodes errorCode)
{
bool isTimeout = false;
switch(errorCode)
{
case NET_HTTPSTATUS_REQUEST_TIMEOUT:
case NET_HTTPSTATUS_INTERNAL_SERVER_ERROR:
case NET_HTTPSTATUS_SERVICE_UNAVAILABLE:
isTimeout = true;
break;
default:
break;
}
return isTimeout;
}
// eof