330 lines
11 KiB
C++
330 lines
11 KiB
C++
#include "CPropertyNameGenerator.h"
|
|
#include "IUIRelay.h"
|
|
#include "Core/Resource/Script/CGameTemplate.h"
|
|
#include "Core/Resource/Script/NPropertyMap.h"
|
|
#include <Common/Hash/CCRC32.h>
|
|
|
|
#include <memory>
|
|
#include <thread>
|
|
|
|
/** Default constructor */
|
|
CPropertyNameGenerator::CPropertyNameGenerator() = default;
|
|
|
|
void CPropertyNameGenerator::Warmup()
|
|
{
|
|
std::unique_lock lock{mWarmupMutex};
|
|
|
|
if (mWordListLoadFinished)
|
|
{
|
|
return;
|
|
}
|
|
mWordListLoadFinished = false;
|
|
mWords.clear();
|
|
|
|
// Load the word list from the file
|
|
using FILEPtr = std::unique_ptr<FILE, decltype(&std::fclose)>;
|
|
auto pListFile = FILEPtr{std::fopen(*(gDataDir + "resources/WordList.txt"), "r"), std::fclose};
|
|
ASSERT(pListFile);
|
|
|
|
while (!feof(pListFile.get()))
|
|
{
|
|
char WordBuffer[64];
|
|
std::fgets(WordBuffer, sizeof(WordBuffer), pListFile.get());
|
|
WordBuffer[0] = TString::CharToUpper(WordBuffer[0]);
|
|
|
|
mWords.emplace_back(TString(WordBuffer).Trimmed());
|
|
}
|
|
|
|
mWordListLoadFinished = true;
|
|
}
|
|
|
|
void CPropertyNameGenerator::Generate(const SPropertyNameGenerationParameters& rkParams, IProgressNotifier* pProgress)
|
|
{
|
|
// Make sure all prerequisite data is loaded!
|
|
ASSERT(!mIsRunning);
|
|
ASSERT(rkParams.TypeNames.size() > 0);
|
|
mGeneratedNames.clear();
|
|
mValidTypePairMap.clear();
|
|
mIsRunning = true;
|
|
|
|
// Convert the type pair map.
|
|
// Also, replace the normal type name list with whatever is in the ID pairs list we were given.
|
|
if (!rkParams.ValidIdPairs.empty())
|
|
{
|
|
mTypeNames.clear();
|
|
|
|
for (const SPropertyIdTypePair& kPair : rkParams.ValidIdPairs)
|
|
{
|
|
mValidTypePairMap[ kPair.ID ] = kPair.pkType;
|
|
NBasics::VectorAddUnique( mTypeNames, TString(kPair.pkType) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mTypeNames = rkParams.TypeNames;
|
|
}
|
|
|
|
// If TestIntsAsChoices is enabled, and int is in the type list, then choice must be in the type list too.
|
|
if (rkParams.TestIntsAsChoices && NBasics::VectorFind(mTypeNames, TString("int")) >= 0)
|
|
{
|
|
NBasics::VectorAddUnique(mTypeNames, TString("choice"));
|
|
}
|
|
|
|
// If we haven't loaded the word list yet, load it.
|
|
Warmup();
|
|
|
|
// Calculate the number of steps involved in this task.
|
|
const size_t kNumWords = mWords.size();
|
|
const int kMaxWords = rkParams.MaxWords;
|
|
TotalTests = 1;
|
|
|
|
for (int i = 0; i < kMaxWords; i++)
|
|
TotalTests *= kNumWords;
|
|
|
|
pProgress->SetOneShotTask("Generating property names");
|
|
pProgress->Report(0, TotalTests);
|
|
|
|
const uint WordsPerThread = kNumWords / rkParams.ConcurrentTasks;
|
|
std::vector<std::thread> Threads;
|
|
for (int i = 0; i < rkParams.ConcurrentTasks; ++i)
|
|
{
|
|
SPropertyNameGenerationTaskParameters Params{};
|
|
Params.TaskIndex = i;
|
|
Params.StartWord = WordsPerThread * i;
|
|
if (i == rkParams.ConcurrentTasks - 1)
|
|
{
|
|
// Ensure last task takes any remaining words
|
|
Params.EndWord = kNumWords - 1;
|
|
}
|
|
else
|
|
{
|
|
Params.EndWord = Params.StartWord + WordsPerThread;
|
|
}
|
|
Threads.emplace_back(&CPropertyNameGenerator::GenerateTask, this, rkParams, Params, pProgress);
|
|
}
|
|
for (auto& Thread : Threads)
|
|
{
|
|
Thread.join();
|
|
}
|
|
|
|
mIsRunning = false;
|
|
}
|
|
|
|
void CPropertyNameGenerator::GenerateTask(const SPropertyNameGenerationParameters& rkParams,
|
|
SPropertyNameGenerationTaskParameters taskParams,
|
|
IProgressNotifier* pProgress)
|
|
{
|
|
const size_t kNumWords = mWords.size();
|
|
const int kMaxWords = rkParams.MaxWords;
|
|
|
|
// Configure params needed to run the name generation!
|
|
bool WriteToLog = rkParams.PrintToLog;
|
|
bool SaveResults = true;
|
|
uint64 TestsDone = 0;
|
|
|
|
// The prefix only needs to be hashed this one time
|
|
CCRC32 PrefixHash;
|
|
PrefixHash.Hash( *rkParams.Prefix );
|
|
|
|
// Use a stack to keep track of the current word we are on. We can use this
|
|
// to cache the hash of a word and then re-use it later instead of recaculating
|
|
// the same hashes over and over. Init the stack with the first word.
|
|
struct SWordCache
|
|
{
|
|
uint WordIndex;
|
|
CCRC32 Hash;
|
|
};
|
|
std::vector<SWordCache> WordCache;
|
|
|
|
SWordCache FirstWord { taskParams.StartWord - 1, CCRC32() };
|
|
WordCache.push_back(FirstWord);
|
|
|
|
while ( true )
|
|
{
|
|
// Increment the current word, handle wrapping back to 0, and update cached hashes as needed.
|
|
int RecalcIndex = WordCache.size() - 1;
|
|
WordCache.back().WordIndex++;
|
|
|
|
while (WordCache[RecalcIndex].WordIndex >= kNumWords ||
|
|
(RecalcIndex == 0 && WordCache[0].WordIndex >= taskParams.EndWord))
|
|
{
|
|
if (RecalcIndex == 0)
|
|
{
|
|
WordCache[0].WordIndex = taskParams.StartWord;
|
|
|
|
SWordCache NewWord { 0, CCRC32() };
|
|
WordCache.push_back(NewWord);
|
|
}
|
|
else
|
|
{
|
|
WordCache[RecalcIndex].WordIndex = 0;
|
|
|
|
RecalcIndex--;
|
|
WordCache[RecalcIndex].WordIndex++;
|
|
}
|
|
}
|
|
|
|
// If we've hit the word limit, break out and end the name generation system.
|
|
if (WordCache.size() > kMaxWords)
|
|
break;
|
|
|
|
// Now that all words are updated, calculate the new hashes.
|
|
CCRC32 LastValidHash = (RecalcIndex > 0 ? WordCache[RecalcIndex - 1].Hash : PrefixHash);
|
|
|
|
for (; RecalcIndex < WordCache.size(); RecalcIndex++)
|
|
{
|
|
int Index = WordCache[RecalcIndex].WordIndex;
|
|
|
|
// For camelcase, hash the first letter of the first word as lowercase
|
|
if (RecalcIndex == 0 && rkParams.Casing == ENameCasing::camelCase)
|
|
{
|
|
const char* pkWord = *mWords[Index];
|
|
LastValidHash.Hash( TString::CharToLower( pkWord[0] ) );
|
|
LastValidHash.Hash( &pkWord[1] );
|
|
}
|
|
else
|
|
{
|
|
// Add an underscore for snake case
|
|
if (RecalcIndex > 0 && rkParams.Casing == ENameCasing::Snake_Case)
|
|
LastValidHash.Hash("_");
|
|
|
|
LastValidHash.Hash( *mWords[Index] );
|
|
}
|
|
|
|
WordCache[RecalcIndex].Hash = LastValidHash;
|
|
}
|
|
|
|
// We got our hash yay! Now hash the suffix and then we can test with each type name
|
|
CCRC32 BaseHash = LastValidHash;
|
|
BaseHash.Hash( *rkParams.Suffix );
|
|
|
|
for (const auto& typeName : mTypeNames)
|
|
{
|
|
CCRC32 FullHash = BaseHash;
|
|
const char* pkTypeName = *typeName;
|
|
FullHash.Hash( pkTypeName );
|
|
const uint32 PropertyID = FullHash.Digest();
|
|
|
|
// Check if this hash is a property ID
|
|
if (IsValidPropertyID(PropertyID, pkTypeName, rkParams))
|
|
{
|
|
std::unique_lock lock{mPropertyCheckMutex};
|
|
|
|
SGeneratedPropertyName PropertyName;
|
|
NPropertyMap::RetrieveXMLsWithProperty(PropertyID, pkTypeName, PropertyName.XmlList);
|
|
|
|
// Generate a string with the complete name. (We wait to do this until now to avoid needless string allocation)
|
|
PropertyName.Name = rkParams.Prefix;
|
|
|
|
for (size_t WordIdx = 0; WordIdx < WordCache.size(); WordIdx++)
|
|
{
|
|
const int Index = WordCache[WordIdx].WordIndex;
|
|
|
|
if (WordIdx > 0 && rkParams.Casing == ENameCasing::Snake_Case)
|
|
{
|
|
PropertyName.Name += "_";
|
|
}
|
|
|
|
PropertyName.Name += mWords[Index];
|
|
}
|
|
|
|
if (rkParams.Casing == ENameCasing::camelCase)
|
|
{
|
|
PropertyName.Name[0] = TString::CharToLower( PropertyName.Name[0] );
|
|
}
|
|
|
|
PropertyName.Name += rkParams.Suffix;
|
|
PropertyName.Type = pkTypeName;
|
|
PropertyName.ID = PropertyID;
|
|
|
|
if (SaveResults)
|
|
{
|
|
mGeneratedNames.push_back(PropertyName);
|
|
|
|
// Check if we have too many saved results. This can cause memory issues and crashing.
|
|
// If we have too many saved results, then to avoid crashing we will force enable log output.
|
|
if (mGeneratedNames.size() > 9999)
|
|
{
|
|
gpUIRelay->ShowMessageBoxAsync("Warning", "There are over 10,000 results. Results will no longer print to the screen. Check the log for the remaining output.");
|
|
WriteToLog = true;
|
|
SaveResults = false;
|
|
}
|
|
}
|
|
|
|
// Log this out
|
|
if ( WriteToLog )
|
|
{
|
|
TString DelimitedXmlList;
|
|
|
|
for (const auto& xml : PropertyName.XmlList)
|
|
{
|
|
DelimitedXmlList += xml + '\n';
|
|
}
|
|
|
|
debugf("%s [%s] : 0x%08X\n%s", *PropertyName.Name, *PropertyName.Type, PropertyName.ID, *DelimitedXmlList);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Every 5000 tests, check with the progress notifier. Update the progress
|
|
// bar and check whether the user has requested to cancel the operation.
|
|
TestsDone++;
|
|
|
|
if ( (TestsDone % 5000) == 0 )
|
|
{
|
|
if (pProgress->ShouldCancel())
|
|
break;
|
|
|
|
std::unique_lock lock{mWarmupMutex};
|
|
auto Value = TotalTestsDone += 5000;
|
|
pProgress->Report(Value, TotalTests);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Returns whether a given property ID is valid */
|
|
bool CPropertyNameGenerator::IsValidPropertyID(uint32 ID, const char*& pkType, const SPropertyNameGenerationParameters& rkParams)
|
|
{
|
|
if (!mValidTypePairMap.empty())
|
|
{
|
|
auto Find = mValidTypePairMap.find(ID);
|
|
|
|
if (Find != mValidTypePairMap.end())
|
|
{
|
|
if (strcmp( Find->second, pkType ) == 0)
|
|
{
|
|
return true;
|
|
}
|
|
else if (rkParams.TestIntsAsChoices && strcmp(pkType, "choice") == 0)
|
|
{
|
|
if (strcmp( Find->second, "int" ) == 0)
|
|
{
|
|
pkType = "int";
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
bool IsAlreadyNamed;
|
|
bool IsValid = NPropertyMap::IsValidPropertyID(ID, pkType, &IsAlreadyNamed);
|
|
|
|
if (!IsValid && rkParams.TestIntsAsChoices && strcmp(pkType, "choice") == 0)
|
|
{
|
|
IsValid = NPropertyMap::IsValidPropertyID(ID, "int", &IsAlreadyNamed);
|
|
|
|
if (IsValid)
|
|
{
|
|
pkType = "int";
|
|
}
|
|
}
|
|
|
|
return IsValid && (!IsAlreadyNamed || !rkParams.ExcludeAccuratelyNamedProperties);
|
|
}
|
|
}
|