Fingolfin's patch for improving dictionary loading speed. Applied as is.

svn-id: r25503
This commit is contained in:
Eugene Sandulenko 2007-02-12 00:21:30 +00:00
parent d8d2c4cd65
commit f2dff4dec6
3 changed files with 141 additions and 103 deletions

View File

@ -559,6 +559,8 @@ AgiEngine::AgiEngine(OSystem *syst) : Engine(syst) {
_objects = NULL;
_oldMode = -1;
_searchTreeRoot = 0;
}
void AgiEngine::initialize() {

View File

@ -104,9 +104,9 @@ enum AgiGameType {
};
enum AgiGameFeatures {
AGI_MOUSE = (1 << 0),
AGI_AGDS = (1 << 1),
AGI_MACGOLDRUSH = (1 << 2)
AGI_MOUSE = 1 << 0,
AGI_AGDS = 1 << 1,
AGI_MACGOLDRUSH = 1 << 2
};
struct AGIGameDescription;
@ -477,6 +477,7 @@ public:
class GfxMgr;
class SpritesMgr;
class Menu;
class SearchTree;
extern struct Mouse g_mouse;
@ -756,16 +757,14 @@ private:
void loadDict(void);
bool matchWord(void);
typedef Common::HashMap<String, String, Common::CaseSensitiveString_Hash, Common::CaseSensitiveString_EqualTo> DictMap;
DictMap _dict;
Common::StringList _dictKeys;
SearchTree *_searchTreeRoot;
SearchTree *_activeTreeNode;
void insertSearchNode(const char *word);
String _currentCode;
String _currentWord;
String _matchedWord;
int _wordNumber;
uint _wordPosition;
bool _nextIsActive;
bool _addIsActive;
public:
char _predictiveResult[40];
};

View File

@ -32,6 +32,80 @@ namespace Agi {
#define kModeNum 1
#define kModeAbc 2
static byte s_asciiToNumTable[256];
void setAsciiToNumTableBatch(const char *chars, byte value) {
while (*chars) {
s_asciiToNumTable[tolower(*chars)] = value;
s_asciiToNumTable[toupper(*chars)] = value;
chars++;
}
}
void initAsciiToNumTable() {
memset(s_asciiToNumTable, 0, sizeof(s_asciiToNumTable));
setAsciiToNumTableBatch("1'-.&", 1);
setAsciiToNumTableBatch("2abc", 2);
setAsciiToNumTableBatch("3def", 3);
setAsciiToNumTableBatch("4ghi", 4);
setAsciiToNumTableBatch("5jkl", 5);
setAsciiToNumTableBatch("6mno", 6);
setAsciiToNumTableBatch("7pqrs", 7);
setAsciiToNumTableBatch("8tuv", 8);
setAsciiToNumTableBatch("9wxyz", 9);
}
class SearchTree {
public:
//byte val;
//SearchTree *parent; // TODO: Could be used to speed up re-searches
SearchTree *children[10];
Common::StringList words;
SearchTree() {
memset(children, 0, sizeof(children));
}
SearchTree *getChild(byte val) {
assert(val < 10);
if (children[val] == 0) {
children[val] = new SearchTree();
}
return children[val];
}
SearchTree *findChildWithWords() {
if (!words.empty())
return this;
SearchTree *child = 0;
for (int i = 0; i < 10 && !child; ++i) {
if (children[i])
child = children[i]->findChildWithWords();
}
return child;
}
};
void AgiEngine::insertSearchNode(const char *word) {
// Insert the word into the tree
SearchTree *tree = _searchTreeRoot;
assert(tree);
for (int i = 0; word[i] != 0; ++i) {
byte key = s_asciiToNumTable[(int)word[i]];
if (key == 0)
return; // abort!
tree = tree->getChild(key);
}
// TODO: Sort words, remove duplicates... ?
tree->words.push_back(word);
}
bool AgiEngine::predictiveDialog(void) {
int key, active = 0;
bool rc = false;
@ -40,6 +114,10 @@ bool AgiEngine::predictiveDialog(void) {
int bx[17], by[17];
String prefix = "";
char temp[MAXWORDLEN + 1];
// FIXME: Move this to a more appropriate place.
initAsciiToNumTable();
const char *buttonStr[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" };
const char *buttons[] = {
@ -62,9 +140,9 @@ bool AgiEngine::predictiveDialog(void) {
};
const char *modes[] = { "Pre", "123", "Abc" };
if (!_dict.size()) {
if (!_searchTreeRoot) {
loadDict();
if (!_dict.size())
if (!_searchTreeRoot)
return false;
}
@ -106,12 +184,10 @@ bool AgiEngine::predictiveDialog(void) {
_gfx->getKey();
}
_wordPosition = 0;
_currentCode = "";
_currentWord = "";
_matchedWord = "";
_wordNumber = 0;
_nextIsActive = _addIsActive = false;
_activeTreeNode = 0;
int mode = kModePre;
@ -123,9 +199,11 @@ bool AgiEngine::predictiveDialog(void) {
int color1 = colors[i * 2];
int color2 = colors[i * 2 + 1];
if (i == 9 && !_nextIsActive) { // Next
if (i == 9 && !(_activeTreeNode && _activeTreeNode->words.size() > 1)) { // Next
color2 = 7;
}
bool _addIsActive = false; // FIXME
if (i == 10 && !_addIsActive) { // Add
color2 = 7;
}
@ -172,23 +250,19 @@ bool AgiEngine::predictiveDialog(void) {
prefix += temp;
prefix += " ";
_wordPosition = 0;
_currentCode = "";
} if (active < 9 || active == 11 || active == 15) { // number or backspace
if (active == 11) { // backspace
if (_currentCode.size()) {
_currentCode.deleteLastChar();
_wordPosition--;
} else {
if (prefix.size())
prefix.deleteLastChar();
}
} else if (active == 15) { // zero
_currentCode += buttonStr[9];
_wordPosition++;
} else {
_currentCode += buttonStr[active];
_wordPosition++;
}
if (mode == kModeNum) {
@ -196,23 +270,14 @@ bool AgiEngine::predictiveDialog(void) {
} else if (mode == kModePre) {
if (!matchWord() && _currentCode.size()) {
_currentCode.deleteLastChar();
_wordPosition--;
matchWord();
}
}
} else if (active == 9) { // next
if (_nextIsActive) {
int wordsNumber = (_matchedWord.size() + 1) / _currentCode.size();
int start;
_wordNumber = (_wordNumber + 1) % wordsNumber;
start = _wordNumber * (_currentCode.size() + 1);
strncpy(temp, _matchedWord.c_str() + start, _currentCode.size());
temp[_matchedWord.size() + 1] = 0;
_currentWord = temp;
int totalWordsNumber = _activeTreeNode ? _activeTreeNode->words.size() : 0;
if (totalWordsNumber > 0) {
_wordNumber = (_wordNumber + 1) % totalWordsNumber;
_currentWord = String(_activeTreeNode->words[_wordNumber].c_str(), _currentCode.size());
}
} else if (active == 10) { // add
debug(0, "add");
@ -268,100 +333,72 @@ static char *rtrim(char *t) {
void AgiEngine::loadDict(void) {
Common::File in;
char buf[MAXLINELEN];
int words = 0, lines = 0;
if (!in.open("pred.txt"))
return;
_searchTreeRoot = new SearchTree();
words = 0;
while (!in.eos() && in.readLine(buf, MAXLINELEN)) {
// Skip leading & trailing whitespaces
char *t = rtrim(ltrim(buf));
char *k = t;
char *word = rtrim(ltrim(buf));
// Skip empty lines
if (*t == 0)
if (*word == 0)
continue;
// First comes the key, scan until we encounter a space
while (!isspace(*t)) {
t++;
}
if (*t == 0)
continue; // No value was specified, skip this
*t++ = 0; // Terminate the key with a zero byte
String key(k);
// Skip over whitespaces
while (isspace(*t))
t++;
lines++;
_dict[key] = String(t);
_dictKeys.push_back(key);
}
// Sort _dictKeys using combsort11 (since we expect pre-sorted data,
// this is fast enough for our purposes).
// See http://en.wikipedia.org/wiki/Comb_sort
uint gap = _dictKeys.size();
bool swaps = false;
while (gap != 1 || swaps) {
if (gap > 1) {
gap = (uint)(gap / 1.3);
if (gap == 10 || gap == 9) gap = 11;
}
swaps = false;
for (uint i = 0; i + gap < _dictKeys.size(); ++i) {
if (_dictKeys[i] > _dictKeys[i + gap]) {
SWAP(_dictKeys[i], _dictKeys[i + gap]);
swaps = true;
// The lines are of the form: "1234 word word"
// I.e. first comes a T9 number, then a space separated list of
// words with that T9 code. We simply ignore the T9 code, and then
// insert the words in order of occurance.
char *tok = strtok(word, " \t");
if (tok) {
while ((tok = strtok(NULL, " ")) != NULL) {
insertSearchNode(tok);
words++;
}
}
}
debug(0, "Loaded %d keys", _dict.size());
debug(0, "Loaded %d lines with %d words", lines, words);
}
bool AgiEngine::matchWord(void) {
_addIsActive = false;
if (!_currentCode.size()) {
if (_currentCode.empty()) {
return false;
}
// Lookup word in the search tree
SearchTree *tree = _searchTreeRoot;
assert(tree);
for (uint i = 0; i < _currentCode.size(); ++i) {
int key = _currentCode[i] - '0';
if (key < 1 || key > 9) {
tree = 0;
break; // Invalid key/code value, abort!
}
tree = tree->children[key];
if (!tree)
break; // No matching entry in the search tree, abort!
}
if (tree)
tree = tree->findChildWithWords();
if (_dict.contains(_currentCode)) {
_currentWord = _matchedWord = _dict[_currentCode];
_wordNumber = 0;
_activeTreeNode = tree;
_currentWord.clear();
_nextIsActive = ((_matchedWord.size() + 1) / _currentCode.size() > 1);
return true;
if (tree) {
_currentWord = String(_activeTreeNode->words[_wordNumber].c_str(), _currentCode.size());
}
// Else search first partial match
for (uint i = 0; i < _dictKeys.size(); i++) {
bool matched = true;
if (_dictKeys[i].size() < _wordPosition)
continue;
for (uint j = 0; j < _dictKeys[i].size() && j < _wordPosition; j++) {
if (_currentCode[j] != _dictKeys[i][j]) {
matched = false;
break;
}
}
if (matched && _dictKeys[i].size() >= _wordPosition) {
_currentWord = _matchedWord = _dict[_dictKeys[i]];
_nextIsActive = ((_matchedWord.size() + 1) / _currentCode.size() > 1);
return true;
}
}
_currentWord = _matchedWord = "";
_nextIsActive = false;
_addIsActive = true;
return false;
return tree != 0;
}
} // End of namespace Agi