added caching of data files to speed game loading

This commit is contained in:
Dave White 2004-05-07 21:32:51 +00:00
parent f69fe0564c
commit 565603f5de
13 changed files with 262 additions and 86 deletions

View file

@ -1123,24 +1123,26 @@ void calculate_healing(display& disp, const gamemap& map,
}
h->second = 0;
} else if(h->second < 0) {
if(show_healing) {
sound::play_sound("groan.wav");
disp.float_label(h->first,lexical_cast<std::string>(h->second*-1),255,0,0);
} else {
if(h->second > 0 && h->second > u.max_hitpoints()-u.hitpoints()) {
h->second = u.max_hitpoints()-u.hitpoints();
if(h->second <= 0)
continue;
}
} else if(h->second > 0) {
if(show_healing) {
sound::play_sound("heal.wav");
disp.float_label(h->first,lexical_cast<std::string>(h->second),0,255,0);
if(h->second < 0) {
if(show_healing) {
sound::play_sound("groan.wav");
disp.float_label(h->first,lexical_cast<std::string>(h->second*-1),255,0,0);
}
} else if(h->second > 0) {
if(show_healing) {
sound::play_sound("heal.wav");
disp.float_label(h->first,lexical_cast<std::string>(h->second),0,255,0);
}
}
}
if(h->second > 0 && h->second > u.max_hitpoints()-u.hitpoints()) {
h->second = u.max_hitpoints()-u.hitpoints();
if(h->second <= 0)
continue;
}
while(h->second > 0) {
const Uint16 heal_colour = disp.rgb(0,0,200);
u.heal(1);

View file

@ -69,43 +69,35 @@ struct close_FILE
void read_file_internal(const std::string& fname, std::string& res)
{
const int size = file_size(fname);
if(size == -1) {
if(size < 0) {
return;
}
res.resize(size);
std::vector<char> v;
v.reserve(size);
const util::scoped_resource<FILE*,close_FILE> file(fopen(fname.c_str(),"rb"));
if(file == NULL) {
return;
}
const int block_size = 4096;
char buf[block_size];
const int block_size = 65536;
std::string::iterator i = res.begin();
while(v.size() < size) {
const size_t expected = minimum<size_t>(block_size,size - v.size());
for(;;) {
const size_t nbytes = fread(buf,1,block_size,file);
//make sure the string is big enough
const int size_diff = (res.end() - i) - nbytes;
if(size_diff < 0) {
res.resize(res.size() - size_diff);
i = res.end() - nbytes;
}
std::copy(buf,buf+nbytes,i);
i += nbytes;
if(nbytes < block_size) {
if(i != res.end()) {
res.erase(i,res.end());
if(expected > 0) {
v.resize(v.size() + expected);
const size_t nbytes = fread(&v[v.size() - expected],1,expected,file);
if(nbytes < expected) {
v.resize(v.size() - (expected - nbytes));
break;
}
return;
}
}
res.resize(v.size());
std::copy(v.begin(),v.end(),res.begin());
}
} //end anon namespace
@ -134,13 +126,21 @@ std::string read_file(const std::string& fname)
//throws io_exception if an error occurs
void write_file(const std::string& fname, const std::string& data)
{
std::ofstream file(fname.c_str());
if(!file.good()) {
std::cerr << "error writing to file: '" << fname << "'\n";
throw io_exception("Error writing to file: " + fname);
const util::scoped_resource<FILE*,close_FILE> file(fopen(fname.c_str(),"wb"));
if(file.get() == NULL) {
throw io_exception("Could not open file for writing: '" + fname + "'");
}
for(std::string::const_iterator i = data.begin(); i != data.end(); ++i) {
file << *i;
const size_t block_size = 4096;
char buf[block_size];
for(size_t i = 0; i < data.size(); i += block_size) {
const size_t bytes = minimum<size_t>(block_size,data.size() - i);
std::copy(data.begin() + i, data.begin() + i + bytes,buf);
const size_t res = fwrite(buf,1,bytes,file.get());
if(res != bytes) {
throw io_exception("Error writing to file: '" + fname + "'");
}
}
}
@ -787,15 +787,22 @@ std::string config::write() const
// it is mapped to the first available character. Any attribute found is always followed
// by a nul-delimited string which is the value for the attribute.
//
// once the number of words in the schema exceeds 'compress_extended_word', the schema
// will begin using two-byte word codes. Any word code which has a character above
// 'compress_extended_word' is assumed to be part of a two-byte word code, and the second
// byte will be read as part of that word code
//
// the schema objects are designed to be persisted. That is, in a network game, both peers
// can store their schema objects, and so rather than sending schema data each time, the peers
// use and build their schemas as the game progresses, adding a new word to the schema anytime
// it is required.
namespace {
const unsigned char compress_open_element = 0, compress_close_element = 1,
compress_schema_item = 2, compress_literal_word = 3,
compress_first_word = 4, compress_last_word = 255;
const size_t compress_max_words = compress_last_word - compress_first_word + 1;
const unsigned int compress_open_element = 0, compress_close_element = 1,
compress_schema_item = 2, compress_literal_word = 3,
compress_first_word = 4, compress_extended_word = 250,
compress_end_words = 256;
const size_t compress_max_basewords = compress_extended_word - compress_first_word;
const size_t compress_max_words = compress_max_basewords + (compress_end_words - compress_extended_word)*compress_end_words;
void compress_output_literal_word(const std::string& word, std::vector<char>& output)
{
@ -806,12 +813,13 @@ namespace {
compression_schema::word_char_map::const_iterator add_word_to_schema(const std::string& word, compression_schema& schema)
{
const unsigned char c = compress_first_word + schema.word_to_char.size();
std::cerr << "inserting in schema: " << int(c) << " -> '" << word << "'\n";
unsigned int c = compress_first_word + schema.word_to_char.size();
if(c >= compress_extended_word) {
c = ((compress_extended_word - 1 + c/compress_extended_word) << 8) + (c%compress_extended_word);
}
schema.char_to_word.insert(std::pair<unsigned char,std::string>(c,word));
return schema.word_to_char.insert(std::pair<std::string,unsigned char>(word,c)).first;
schema.char_to_word.insert(std::pair<unsigned int,std::string>(c,word));
return schema.word_to_char.insert(std::pair<std::string,unsigned int>(word,c)).first;
}
compression_schema::word_char_map::const_iterator get_word_in_schema(const std::string& word, compression_schema& schema, std::vector<char>& output)
@ -831,7 +839,6 @@ namespace {
return add_word_to_schema(word,schema);
} else {
//it's not there, and there's no room to add it
std::cerr << "no room for word '" << word << "' in schema\n";
return schema.word_to_char.end();
}
}
@ -842,7 +849,12 @@ namespace {
const compression_schema::word_char_map::const_iterator w = get_word_in_schema(word,schema,res);
if(w != schema.word_to_char.end()) {
//the word is in the schema, all we have to do is output the compression code for it.
res.push_back(w->second);
if(w->second >= compress_extended_word) {
res.push_back(w->second >> 8);
res.push_back(w->second & 8);
} else {
res.push_back(w->second);
}
} else {
//the word is not in the schema. Output it as a literal word
res.push_back(char(compress_literal_word));
@ -922,9 +934,18 @@ std::string::const_iterator config::read_compressed_internal(std::string::const_
if(*i1 == compress_literal_word) {
i1 = compress_read_literal_word(i1+1,i2,word);
} else {
const compression_schema::char_word_map::const_iterator itor = schema.char_to_word.find(*i1);
const unsigned char c = *i1;
unsigned int code = c;
if(code >= compress_extended_word) {
++i1;
const unsigned char c = *i1;
code <<= 8;
code += c;
}
const compression_schema::char_word_map::const_iterator itor = schema.char_to_word.find(code);
if(itor == schema.char_to_word.end()) {
std::cerr << "illegal char: " << int(*i1) << "\n";
std::cerr << "illegal word code: " << code << "\n";
throw error("Illegal character in compression input\n");
}

View file

@ -86,10 +86,10 @@ typedef std::map<std::string,std::string> string_map;
//this object holds the schema by which config objects can be compressed and decompressed.
struct compression_schema
{
typedef std::map<unsigned char,std::string> char_word_map;
typedef std::map<unsigned int,std::string> char_word_map;
char_word_map char_to_word;
typedef std::map<std::string,unsigned char> word_char_map;
typedef std::map<std::string,unsigned int> word_char_map;
word_char_map word_to_char;
};

View file

@ -65,7 +65,7 @@ display::display(unit_map& units, CVideo& video, const gamemap& map,
deadAmount_(0.0), advancingAmount_(0.0), updatesLocked_(0),
turbo_(false), grid_(false), sidebarScaling_(1.0),
theme_(theme_cfg,screen_area()), builder_(built_terrains, map),
firstTurn_(true), map_labels_(*this,map),
first_turn_(true), in_game_(false), map_labels_(*this,map),
tod_hex_mask1(NULL), tod_hex_mask2(NULL)
{
if(non_interactive())
@ -102,7 +102,7 @@ void display::new_turn()
{
const time_of_day& tod = status_.get_time_of_day();
if(!turbo() && !firstTurn_) {
if(!turbo() && !first_turn_) {
image::set_image_mask("");
const time_of_day& old_tod = status_.get_previous_time_of_day();
@ -141,7 +141,7 @@ void display::new_turn()
tod_hex_mask2.assign(NULL);
}
firstTurn_ = false;
first_turn_ = false;
image::set_colour_adjustment(tod.red,tod.green,tod.blue);
image::set_image_mask(tod.image_mask);
@ -2071,6 +2071,12 @@ const theme::menu* display::menu_pressed(int mousex, int mousey, bool button_pre
return NULL;
}
void display::begin_game()
{
in_game_ = true;
create_buttons();
}
void display::create_buttons()
{
std::cerr << "clearing buttons...\n";

View file

@ -319,7 +319,9 @@ public:
void remove_highlighted_loc(const gamemap::location &hex);
void create_buttons();
void begin_game();
bool in_game() const { return in_game_; }
private:
display(const display&);
@ -422,9 +424,10 @@ private:
theme theme_;
terrain_builder builder_;
void create_buttons();
std::vector<gui::button> buttons_;
bool firstTurn_;
bool first_turn_, in_game_;
std::set<std::string> observers_;

View file

@ -153,6 +153,12 @@ std::string get_saves_dir()
return get_dir(dir_path);
}
std::string get_cache_dir()
{
const std::string dir_path = get_user_data_dir() + "/cache";
return get_dir(dir_path);
}
std::string get_dir(const std::string& dir_path)
{
#ifdef _WIN32
@ -271,6 +277,35 @@ time_t file_create_time(const std::string& fname)
return buf.st_mtime;
}
time_t file_tree_modified_time(const std::string& path, time_t tm)
{
std::vector<std::string> files, dirs;
get_files_in_dir(path,&files,&dirs,ENTIRE_FILE_PATH);
for(std::vector<std::string>::const_iterator i = files.begin(); i != files.end(); ++i) {
const time_t t = file_create_time(*i);
if(t > tm) {
tm = t;
}
}
for(std::vector<std::string>::const_iterator j = dirs.begin(); j != dirs.end(); ++j) {
tm = file_tree_modified_time(*j,tm);
}
return tm;
}
time_t data_tree_modified_time()
{
static time_t cached_val = 0;
if(cached_val == 0) {
cached_val = file_tree_modified_time("data/");
}
return cached_val;
}
int file_size(const std::string& fname)
{
struct stat buf;

View file

@ -35,6 +35,7 @@ std::string get_dir(const std::string &dir);
//the location of various important files
std::string get_prefs_file();
std::string get_saves_dir();
std::string get_cache_dir();
std::string get_user_data_dir();
//function which returns true iff the given file is a directory
@ -43,8 +44,16 @@ bool is_directory(const std::string& fname);
//function which returns true iff file with name already exists
bool file_exists(const std::string& name);
//function to get the creation time of a file
time_t file_create_time(const std::string& fname);
//function to get the time at which the most recently modified file
//in a directory tree was modified at
time_t file_tree_modified_time(const std::string& path, time_t tm=0);
//function to get the time at which the data/ tree was last modified at
time_t data_tree_modified_time();
//returns the size of a file, or -1 if the file doesn't exist
int file_size(const std::string& fname);

View file

@ -203,8 +203,70 @@ LEVEL_RESULT play_game(display& disp, game_state& state, config& game_config,
return VICTORY;
}
namespace {
//this function reads the game configuration, searching for valid cached copies first
void read_game_cfg(preproc_map& defines, std::vector<line_source>& line_src, config& cfg, bool use_cache)
{
log_scope("read_game_cfg");
if(defines.size() < 4 && use_cache) {
bool is_valid = true;
std::stringstream str;
for(preproc_map::const_iterator i = defines.begin(); i != defines.end(); ++i) {
if(i->second.value != "" || i->second.arguments.empty() == false) {
is_valid = false;
break;
}
str << "-" << i->first;
}
if(is_valid) {
const std::string& cache = get_cache_dir();
if(cache != "") {
const std::string fname = cache + "/game.cfg-cache" + str.str();
if(file_exists(fname) && file_create_time(fname) > data_tree_modified_time()) {
std::cerr << "found valid cache at '" << fname << "' using it\n";
log_scope("read cache");
compression_schema schema;
try {
cfg.read_compressed(read_file(fname),schema);
return;
} catch(config::error&) {
std::cerr << "cache is corrupt. Loading from files\n";
} catch(io_exception&) {
std::cerr << "error reading cache. Loading from files\n";
}
}
std::cerr << "no valid cache found. Writing cache to '" << fname << "'\n";
//read the file and then write to the cache
cfg.read(preprocess_file("data/game.cfg",&defines,&line_src));
try {
compression_schema schema;
write_file(fname,cfg.write_compressed(schema));
} catch(io_exception& e) {
std::cerr << "could not write to cache '" << fname << "'\n";
}
return;
}
}
}
std::cerr << "caching cannot be done. Reading file\n";
cfg.read(preprocess_file("data/game.cfg",&defines,&line_src));
}
}
int play_game(int argc, char** argv)
{
const int start_ticks = SDL_GetTicks();
//parse arguments that shouldn't require a display device
int arg;
for(arg = 1; arg != argc; ++arg) {
@ -222,7 +284,8 @@ int play_game(int argc, char** argv)
<< " --path Prints the name of the game data directory and exits\n"
<< " -t, --test Runs the game in a small example scenario\n"
<< " -w, --windowed Runs the game in windowed mode\n"
<< " -v, --version Prints the game's version number and exits\n";
<< " -v, --version Prints the game's version number and exits\n"
<< " --nocache Disables caching of game data\n";
return 0;
} else if(val == "--version" || val == "-v") {
std::cout << "Battle for Wesnoth " << game_config::version
@ -249,16 +312,21 @@ int play_game(int argc, char** argv)
const events::event_context main_event_context;
std::cerr << "initialized managers\n";
std::cerr << (SDL_GetTicks() - start_ticks) << "\n";
bool test_mode = false, multiplayer_mode = false, no_gui = false;
bool use_caching = true;
for(arg = 1; arg != argc; ++arg) {
const std::string val(argv[arg]);
if(val.empty()) {
continue;
}
if(val == "--resolution" || val == "-r") {
if(val == "--nocache") {
use_caching = false;
} else if(val == "--resolution" || val == "-r") {
if(arg+1 != argc) {
++arg;
const std::string val(argv[arg]);
@ -299,6 +367,7 @@ int play_game(int argc, char** argv)
}
std::cerr << "parsed arguments\n";
std::cerr << (SDL_GetTicks() - start_ticks) << "\n";
if(no_gui && !multiplayer_mode) {
std::cerr << "--nogui flag is only valid with --multiplayer flag\n";
@ -381,18 +450,21 @@ int play_game(int argc, char** argv)
const cursor::manager cursor_manager;
std::cerr << "initialized gui\n";
std::cerr << (SDL_GetTicks() - start_ticks) << "\n";
//load in the game's configuration files
preproc_map defines_map;
defines_map["NORMAL"] = preproc_define();
defines_map["MEDIUM"] = preproc_define();
std::vector<line_source> line_src;
config game_config;
try {
log_scope("loading config");
const std::string game_cfg = preprocess_file("data/game.cfg",&defines_map,&line_src);
game_config.read(game_cfg,&line_src);
std::cerr << (SDL_GetTicks() - start_ticks) << "\n";
read_game_cfg(defines_map,line_src,game_config,use_caching);
std::cerr << (SDL_GetTicks() - start_ticks) << "\n";
} catch(config::error& e) {
display::unit_map u_map;
config dummy_cfg("");
@ -407,6 +479,7 @@ int play_game(int argc, char** argv)
game_config::load_config(game_config.child("game_config"));
std::cerr << "parsed config files\n";
std::cerr << (SDL_GetTicks() - start_ticks) << "\n";
const config::child_list& units = game_config.get_children("units");
if(units.empty()) {
@ -429,6 +502,7 @@ int play_game(int argc, char** argv)
}
std::cerr << "set language\n";
std::cerr << (SDL_GetTicks() - start_ticks) << "\n";
if(!no_gui) {
SDL_WM_SetCaption(string_table["game_title"].c_str(), NULL);
@ -440,6 +514,7 @@ int play_game(int argc, char** argv)
sound::play_music(game_config::title_music);
std::cerr << "started music\n";
std::cerr << (SDL_GetTicks() - start_ticks) << "\n";
game_state state;
@ -449,6 +524,7 @@ int play_game(int argc, char** argv)
std::vector<team>(),dummy_cfg,dummy_cfg);
std::cerr << "initialized display object\n";
std::cerr << (SDL_GetTicks() - start_ticks) << "\n";
if(test_mode) {
state.campaign_type = "test";
@ -599,6 +675,7 @@ int play_game(int argc, char** argv)
recorder.clear();
std::cerr << "showing title screen...\n";
std::cerr << (SDL_GetTicks() - start_ticks) << "\n";
gui::TITLE_RESULT res = gui::CONTINUE;
while(res == gui::CONTINUE) {
@ -608,6 +685,7 @@ int play_game(int argc, char** argv)
std::cerr << "title screen returned result\n";
if(res == gui::QUIT_GAME) {
std::cerr << "quiting game...\n";
return 0;
} else if(res == gui::LOAD_GAME) {
@ -857,7 +935,9 @@ int play_game(int argc, char** argv)
}
//make a new game config item based on the difficulty level
config game_config(preprocess_file("data/game.cfg", &defines_map));
std::vector<line_source> line_src;
config game_config;
read_game_cfg(defines_map,line_src,game_config,use_caching);
const config::child_list& units = game_config.get_children("units");
if(units.empty()) {
@ -868,8 +948,7 @@ int play_game(int argc, char** argv)
game_data units_data(*units[0]);
const LEVEL_RESULT result = play_game(disp,state,game_config,
units_data,video);
const LEVEL_RESULT result = play_game(disp,state,game_config,units_data,video);
if(result == VICTORY) {
gui::show_dialog(disp,NULL,
string_table["end_game_heading"],
@ -884,7 +963,10 @@ int play_game(int argc, char** argv)
int main(int argc, char** argv)
{
try {
return play_game(argc,argv);
std::cerr << "started game: " << SDL_GetTicks() << "\n";
const int res = play_game(argc,argv);
std::cerr << "exiting with code " << res << "\n";
return res;
} catch(CVideo::error&) {
std::cerr << "Could not initialize video. Exiting.\n";
} catch(font::manager::error&) {
@ -895,6 +977,8 @@ int main(int argc, char** argv)
std::cerr << "Could not create button: Image could not be found\n";
} catch(CVideo::quit&) {
//just means the game should quit
} catch(end_level_exception&) {
std::cerr << "caught end_level_exception (quitting)\n";
} catch(...) {
std::cerr << "Unhandled exception. Exiting\n";
}

View file

@ -239,9 +239,9 @@ std::string get_hotkey_name(hotkey_item i)
void key_event(display& disp, const SDL_KeyboardEvent& event, command_executor* executor)
{
if(event.keysym.sym == SDLK_ESCAPE) {
const int res = gui::show_dialog(disp,NULL,"",
string_table["quit_message"],gui::YES_NO);
if(event.keysym.sym == SDLK_ESCAPE && disp.in_game()) {
std::cerr << "escape pressed..showing quit\n";
const int res = gui::show_dialog(disp,NULL,"",string_table["quit_message"],gui::YES_NO);
if(res == 0) {
throw end_level_exception(QUIT);
} else {
@ -400,10 +400,14 @@ void execute_command(display& disp, HOTKEY_COMMAND command, command_executor* ex
executor->search();
break;
case HOTKEY_QUIT_GAME: {
const int res = gui::show_dialog(disp,NULL,"",string_table["quit_message"],gui::YES_NO);
if(res == 0) {
throw end_level_exception(QUIT);
if(disp.in_game()) {
std::cerr << "is in game -- showing quit message\n";
const int res = gui::show_dialog(disp,NULL,"",string_table["quit_message"],gui::YES_NO);
if(res == 0) {
throw end_level_exception(QUIT);
}
}
break;
}

View file

@ -112,7 +112,6 @@ LEVEL_RESULT play_level(game_data& gameinfo, const config& game_config,
game_state& state_of_game,
const std::vector<config*>& story)
{
std::cerr << "starting level '" << string_table["defeat_message"] << "'\n";
//if the entire scenario should be randomly generated
if((*level)["scenario_generation"] != "") {
std::cerr << "randomly generating scenario...\n";
@ -322,7 +321,7 @@ LEVEL_RESULT play_level(game_data& gameinfo, const config& game_config,
turn_info::floating_textbox textbox_info;
try {
gui.create_buttons();
gui.begin_game();
gui.adjust_colours(0,0,0);
game_events::fire("prestart");

View file

@ -40,7 +40,7 @@ namespace {
int commands_disabled = 0;
}
command_disabler::command_disabler(display* disp) : hotkey::basic_handler(disp)
command_disabler::command_disabler(display* disp)
{
++commands_disabled;
}

View file

@ -51,7 +51,7 @@ private:
display& gui_;
};
struct command_disabler : private hotkey::basic_handler
struct command_disabler
{
command_disabler(display* disp);
~command_disabler();

View file

@ -69,13 +69,28 @@ bool game::filter_commands(network::connection player, config& cfg)
return true;
}
namespace {
std::string describe_turns(int turn, const std::string& num_turns)
{
char buf[50];
sprintf(buf,"%d/",int(turn));
if(num_turns == "-1") {
return buf + std::string("-");
} else {
return buf + num_turns;
}
}
}
void game::start_game()
{
started_ = true;
describe_slots();
if(description()) {
description()->values["turn"] = "1";
description()->values["turn"] = describe_turns(1,level()["turns"]);
}
allow_observers_ = level_["observer"] != "no";
@ -175,9 +190,7 @@ bool game::end_turn()
return false;
}
char buf[50];
sprintf(buf,"%d",int(turn));
desc->values["turn"] = buf;
desc->values["turn"] = describe_turns(int(turn),level()["turns"]);
return true;
}
@ -298,7 +311,7 @@ bool game::player_on_team(const std::string& team, network::connection player) c
}
}
//other hosts than the game host
//hosts other than the game host
const std::map<network::connection,std::string>::const_iterator side = sides_.find(player);
if(side != sides_.end()) {
const config* const side_cfg = level_.find_child("side","side",side->second);