Refactored config_cache to support loading multiple cached items...

...using path as key
This commit is contained in:
Pauli Nieminen 2008-08-26 15:42:26 +00:00
parent 4d39fc6474
commit 99305160ae
7 changed files with 212 additions and 221 deletions

View File

@ -30,6 +30,8 @@
#include "global.hpp"
#include <boost/shared_ptr.hpp>
#include <map>
#include <memory>
#include <ostream>
@ -41,6 +43,10 @@
typedef std::map<std::string,t_string> string_map;
class config;
typedef boost::shared_ptr<config> config_ptr;
/** A config object defines a single node in a WML file, with access to child nodes. */
class config
{

View File

@ -24,6 +24,7 @@
#include "show_dialog.hpp"
#include "sha1.hpp"
#include "serialization/binary_wml.hpp"
#include "serialization/binary_or_text.hpp"
#include "serialization/parser.hpp"
#define ERR_CONFIG LOG_STREAM(err, config)
@ -41,44 +42,14 @@ namespace game_config {
}
config_cache::config_cache() :
game_config_(),
force_valid_cache_(false),
use_cache_(true),
dirty_(true),
config_root_("data/"),
user_config_root_(get_addon_campaigns_dir()),
defines_map_()
{
// To settup initial defines map correctly
clear_defines();
}
std::string config_cache::get_config_root() const
{
return config_root_;
}
void config_cache::set_config_root(const std::string& path)
{
if (path == config_root_)
return;
dirty_ = true;
config_root_ = path;
}
std::string config_cache::get_user_config_root() const
{
return user_config_root_;
}
void config_cache::set_user_config_root(const std::string& path)
{
if (path == user_config_root_)
return;
dirty_ = true;
user_config_root_ = path;
}
const preproc_map& config_cache::get_preproc_map() const
{
return defines_map_;
@ -106,35 +77,59 @@ namespace game_config {
}
void config_cache::reload_translations()
config_ptr config_cache::get_config(const std::string& path)
{
if (dirty_)
config_ptr ret(new config());
load_configs(path, *ret);
return ret;
}
void config_cache::write_file(std::string path, const config& cfg)
{
bool gzip = false;
switch(game_config::cache_type)
{
load_configs(game_config_, false);
} else {
game_config_.reset_translation();
game_config::load_config(game_config_.child("game_config"));
case GZIP:
gzip = true;
path += ".gz";
case BWML:
{
scoped_ostream stream = ostream_file(path);
config_writer writer(*stream,gzip,"",game_config::cache_compression_level);
writer.write(cfg);
}
break;
}
}
config& config_cache::get_config(bool recheck_cache)
void config_cache::read_file(const std::string& path, config& cfg)
{
if (!dirty_)
return game_config_;
load_configs(game_config_, recheck_cache);
dirty_ = false;
return game_config_;
std::string error_log;
switch(game_config::cache_type)
{
case BWML:
{
scoped_istream stream = istream_file(path);
read(cfg, *stream);
}
break;
case GZIP:
{
scoped_istream stream = istream_file(path + ".gz");
read_gz(cfg, *stream);
}
break;
}
}
void config_cache::read_configs(config& cfg, std::string& error_log)
void config_cache::read_configs(const std::string& path, config& cfg)
{
preproc_map defines_map(defines_map_);
std::string user_error_log;
std::string error_log;
//read the file and then write to the cache
scoped_istream stream = preprocess_file(config_root_, &defines_map, &error_log);
scoped_istream stream = preprocess_file(path, &defines_map, &error_log);
//reset the parse counter before reading the game files
if (loadscreen::global_loadscreen) {
@ -142,87 +137,17 @@ namespace game_config {
}
read(cfg, *stream, &error_log);
// clone and put the gfx rules aside so that we can prepend the add-on
// rules to them.
config core_terrain_rules;
// FIXME: there should be a canned algorithm for cloning child_list objects,
// along with the memory their elements point to... little implementation detail.
foreach(config const* p_cfg, game_config_.get_children("terrain_graphics")) {
core_terrain_rules.add_child("terrain_graphics", *p_cfg);
if (!error_log.empty())
{
throw config::error(error_log);
}
game_config_.clear_children("terrain_graphics");
// load usermade add-ons
// const std::string user_campaign_dir = get_addon_campaigns_dir();
std::vector< std::string > error_addons;
// Scan addon directories
std::vector<std::string> user_addons;
get_files_in_dir(user_config_root_,NULL,&user_addons,ENTIRE_FILE_PATH);
// Load the addons
for(std::vector<std::string>::const_iterator uc = user_addons.begin(); uc != user_addons.end(); ++uc) {
std::string oldstyle_cfg = *uc + ".cfg";
std::string main_cfg = *uc + "/_main.cfg";
std::string toplevel;
if (file_exists(oldstyle_cfg))
toplevel = oldstyle_cfg;
else if (file_exists(main_cfg))
toplevel = main_cfg;
else
continue;
try {
preproc_map addon_defines_map(defines_map);
scoped_istream stream = preprocess_file(toplevel, &addon_defines_map);
std::string addon_error_log;
config umc_cfg;
read(umc_cfg, *stream, &addon_error_log);
if (addon_error_log.empty()) {
game_config_.append(umc_cfg);
} else {
user_error_log += addon_error_log;
error_addons.push_back(*uc);
}
} catch(config::error& err) {
ERR_CONFIG << "error reading usermade add-on '" << *uc << "'\n";
error_addons.push_back(*uc);
user_error_log += err.message + "\n";
} catch(preproc_config::error&) {
ERR_CONFIG << "error reading usermade add-on '" << *uc << "'\n";
error_addons.push_back(*uc);
//no need to modify the error log here, already done by the preprocessor
} catch(io_exception&) {
ERR_CONFIG << "error reading usermade add-on '" << *uc << "'\n";
error_addons.push_back(*uc);
}
if(error_addons.empty() == false) {
std::stringstream msg;
msg << _n("The following add-on had errors and could not be loaded:",
"The following add-ons had errors and could not be loaded:",
error_addons.size());
for(std::vector<std::string>::const_iterator i = error_addons.begin(); i != error_addons.end(); ++i) {
msg << "\n" << *i;
}
msg << "\n" << _("ERROR DETAILS:") << "\n" << font::nullify_markup(user_error_log);
gui::show_error_message(*game_display::get_singleton(),msg.str());
}
}
cfg.merge_children("units");
cfg.append(core_terrain_rules);
}
void config_cache::load_configs(config& cfg, bool recheck_cache)
void config_cache::read_cache(const std::string& path, config& cfg)
{
bool is_valid = true;
std::stringstream defines_string;
defines_string << path;
for(preproc_map::const_iterator i = defines_map_.begin(); i != defines_map_.end(); ++i) {
if(i->second.value != "" || i->second.arguments.empty() == false) {
is_valid = false;
@ -232,10 +157,10 @@ namespace game_config {
defines_string << " " << i->first;
}
//std::string localename = get_locale().localename;
//str << "-lang_" << (localename.empty() ? "default" : localename);
if(is_valid && use_cache_) {
// Do cache check only if define map is valid and
// caching is allowed
if(is_valid) {
const std::string& cache = get_cache_dir();
if(cache != "") {
sha1_hash sha(defines_string.str()); // use a hash for a shorter display of the defines
@ -248,8 +173,7 @@ namespace game_config {
try {
if(file_exists(fname_checksum)) {
config checksum_cfg;
scoped_istream stream = istream_file(fname_checksum);
read(checksum_cfg, *stream);
read_file(fname_checksum, checksum_cfg);
dir_checksum = file_tree_checksum(checksum_cfg);
}
} catch(config::error&) {
@ -263,12 +187,11 @@ namespace game_config {
LOG_CONFIG << "skipping cache validation (forced)\n";
}
if(file_exists(fname) && (force_valid_cache_ || (dir_checksum == data_tree_checksum(recheck_cache)))) {
if(file_exists(fname) && (force_valid_cache_ || (dir_checksum == data_tree_checksum()))) {
LOG_CONFIG << "found valid cache at '" << fname << "' using it\n";
log_scope("read cache");
try {
scoped_istream stream = istream_file(fname);
read_compressed(cfg, *stream);
read_file(fname,cfg);
return;
} catch(config::error&) {
ERR_CONFIG << "cache is corrupt. Loading from files\n";
@ -278,34 +201,34 @@ namespace game_config {
}
LOG_CONFIG << "no valid cache found. Writing cache to '" << fname << " with defines_map "<< defines_string.str() << "'\n";
DBG_CONFIG << ((use_cache_ && file_exists(fname)) ? "yes":"no ") << " " << dir_checksum.modified << "==" << data_tree_checksum().modified << " " << dir_checksum.nfiles << "==" << data_tree_checksum().nfiles << " " << dir_checksum.sum_size << "==" << data_tree_checksum().sum_size << "\n";
std::string error_log;
read_configs(path, cfg);
read_configs(cfg, error_log);
if(!error_log.empty()) {
gui::show_error_message(*game_display::get_singleton(),
_("Warning: Errors occurred while loading game configuration files: '") +
font::nullify_markup(error_log));
} else {
try {
scoped_ostream cache = ostream_file(fname);
write_compressed(*cache, cfg);
config checksum_cfg;
data_tree_checksum().write(checksum_cfg);
scoped_ostream checksum = ostream_file(fname_checksum);
write(*checksum, checksum_cfg);
} catch(io_exception&) {
ERR_FS << "could not write to cache '" << fname << "'\n";
}
try {
write_file(fname, cfg);
config checksum_cfg;
data_tree_checksum().write(checksum_cfg);
write_file(fname_checksum, checksum_cfg);
} catch(io_exception&) {
ERR_FS << "could not write to cache '" << fname << "'\n";
}
return;
}
}
LOG_CONFIG << "Loading plain config instead of cache\n";
load_configs(path, cfg);
}
void config_cache::load_configs(const std::string& path, config& cfg)
{
if (use_cache_)
{
read_cache(path, cfg);
} else {
read_configs(path, cfg);
}
return;
ERR_CONFIG << "caching cannot be done. Reading file\n";
std::string error_log;
@ -324,9 +247,23 @@ namespace game_config {
use_cache_ = use;
}
void config_cache::set_force_valid_cache(bool force)
{
force_valid_cache_ = force;
}
void config_cache::recheck_filetree_checksum()
{
data_tree_checksum(true);
}
void config_cache::add_define(const std::string& define)
{
dirty_ = true;
defines_map_[define] = preproc_define();
}
void config_cache::remove_define(const std::string& define)
{
defines_map_.erase(define);
}
}

View File

@ -22,44 +22,96 @@
namespace game_config {
/**
* Singleton object to manage game configs
* and cache reading.
**/
/**
* Singleton object to manage game configs
* and cache reading.
* @TODO: Make smarter filetree checksum caching so only required parts
* of tree are checked at startup. Trees are overlapping so have
* to split trees to subtrees to only do check once per file.
**/
class config_cache : private boost::noncopyable {
static config_cache cache_;
config game_config_;
bool force_valid_cache_, use_cache_, dirty_;
std::string config_root_, user_config_root_;
bool force_valid_cache_, use_cache_;
preproc_map defines_map_;
void read_configs(config&, std::string&);
void read_file(const std::string& file, config& cfg);
void write_file(std::string file, const config& cfg);
void read_cache(const std::string& path, config& cfg);
void read_configs(const std::string& path, config& cfg);
// Protected to let test code access
protected:
config_cache();
std::string get_config_root() const;
std::string get_user_config_root() const;
const preproc_map& get_preproc_map() const;
void load_configs(config& cfg, bool recheck_cache);
void load_configs(const std::string& path, config& cfg);
public:
/**
* Get reference to the singleton object
**/
static config_cache& instance();
void set_config_root(const std::string&);
void set_user_config_root(const std::string&);
config& get_config(bool recheck_cache = false);
/**
* get config object from given path
* @param path which to load. Should be _main.cfg.
* @return shread_ptr config object
**/
config_ptr get_config(const std::string& path);
/**
* Clear stored defines map to default values
**/
void clear_defines();
/**
* Add a entry to preproc defines map
**/
void add_define(const std::string& define);
/**
* Remove a entry to preproc defines map
**/
void remove_define(const std::string& define);
void reload_translations();
/**
* Enable/disable caching
**/
void set_use_cache(bool use);
/**
* Enable/disable cache validation
**/
void set_force_valid_cache(bool force);
/**
* Force cache checksum validation.
**/
void recheck_filetree_checksum();
};
/**
* Used to set and unset scoped defines to preproc_map
* This is prefered form to set defines that aren't global
**/
template <class T>
class scoped_preproc_define_internal : private boost::noncopyable {
// Protected to let test code access
protected:
std::string name_;
public:
scoped_preproc_define_internal(const std::string& name) : name_(name)
{
T::instance().add_define(name_);
}
~scoped_preproc_define_internal()
{
T::instance().remove_define(name_);
}
};
typedef scoped_preproc_define_internal<config_cache> scoped_preproc_define;
}
#endif

View File

@ -59,6 +59,9 @@ namespace game_config
bool use_dummylocales = false;
#endif
cache_types cache_type = BWML;
int cache_compression_level = 5;
std::string game_icon = "wesnoth-icon.png", game_title, game_logo, title_music, lobby_music;
int title_logo_x = 0, title_logo_y = 0, title_buttons_x = 0, title_buttons_y = 0, title_buttons_padding = 0,
title_tip_x = 0, title_tip_width = 0, title_tip_padding = 0;

View File

@ -50,6 +50,13 @@ namespace game_config
extern bool use_dummylocales;
extern enum cache_types {
BWML,
GZIP
} cache_type;
extern int cache_compression_level;
extern std::string path;
struct server_info {

View File

@ -63,7 +63,7 @@ struct wesnoth_global_fixture {
game_config::path = get_cwd();
load_language_list();
::init_textdomains(game_config::config_cache::instance().get_config());
::init_textdomains(*game_config::config_cache::instance().get_config(game_config::path + "/data/_main.cfg"));
const std::vector<language_def>& languages = get_languages();
std::vector<language_def>::const_iterator English = std::find_if(languages.begin(),
languages.end(),

View File

@ -27,6 +27,7 @@
BOOST_AUTO_TEST_SUITE( config_cache )
const std::string test_data_path("data/test/test/_main.cfg");
/**
* Used to make distinct singleton for testing it
* because other tests will need original one to load data
@ -41,18 +42,16 @@ BOOST_AUTO_TEST_SUITE( config_cache )
return cache_;
}
std::string get_config_root() const {
return game_config::config_cache::get_config_root();
}
std::string get_user_config_root() const {
return game_config::config_cache::get_user_config_root();
}
const preproc_map& get_preproc_map() const {
return game_config::config_cache::get_preproc_map();
}
};
/**
* Used to redirect defines settings to test cache
**/
typedef game_config::scoped_preproc_define_internal<test_config_cache> test_scoped_define;
test_config_cache test_config_cache::cache_;
static preproc_map settup_test_preproc_map()
@ -84,9 +83,6 @@ BOOST_AUTO_TEST_CASE( test_config_cache_defaults )
preproc_map defines_map(settup_test_preproc_map());
test_config_cache& cache = test_config_cache::instance();
BOOST_CHECK_EQUAL("data/", cache.get_config_root());
BOOST_CHECK_EQUAL(get_addon_campaigns_dir(), cache.get_user_config_root());
const preproc_map& test_defines = cache.get_preproc_map();
BOOST_CHECK_EQUAL_COLLECTIONS(test_defines.begin(),test_defines.end(),
defines_map.begin() ,defines_map.end());
@ -95,7 +91,7 @@ BOOST_AUTO_TEST_CASE( test_config_cache_defaults )
BOOST_AUTO_TEST_CASE( test_load_config )
{
test_config_cache& cache = test_config_cache::instance();
cache.add_define("TEST");
test_scoped_define test_def("TEST");
preproc_map defines_map(settup_test_preproc_map());
defines_map["TEST"] = preproc_define();
@ -103,12 +99,6 @@ BOOST_AUTO_TEST_CASE( test_load_config )
BOOST_CHECK_EQUAL_COLLECTIONS(test_defines.begin(),test_defines.end(),
defines_map.begin() ,defines_map.end());
std::string test_data_path("data/test/test/");
cache.set_config_root(test_data_path);
BOOST_CHECK_EQUAL(test_data_path, cache.get_config_root());
test_data_path = "invalid";
cache.set_user_config_root(test_data_path);
BOOST_CHECK_EQUAL(test_data_path, cache.get_user_config_root());
config test_config;
config* child = &test_config.add_child("textdomain");
@ -118,59 +108,55 @@ BOOST_AUTO_TEST_CASE( test_load_config )
(*child)["define"] = "test";
BOOST_CHECK_EQUAL(test_config, cache.get_config());
BOOST_CHECK_EQUAL(test_config, *cache.get_config(test_data_path));
cache.add_define("TEST_DEFINE");
test_scoped_define test_define_def("TEST_DEFINE");
child = &test_config.add_child("test_key2");
(*child)["define"] = t_string("testing translation reset.", GETTEXT_DOMAIN);
BOOST_CHECK_EQUAL(test_config, cache.get_config());
BOOST_CHECK_EQUAL(test_config, *cache.get_config(test_data_path));
BOOST_CHECK_EQUAL((*test_config.child("test_key2"))["define"].str(), (*cache.get_config().child("test_key2"))["define"].str());
BOOST_CHECK_EQUAL((*test_config.child("test_key2"))["define"].str(), (*cache.get_config(test_data_path)->child("test_key2"))["define"].str());
}
static bool match_german(const language_def& def)
{
return def.localename == "de_DE";
}
BOOST_AUTO_TEST_CASE( test_translation_reload )
BOOST_AUTO_TEST_CASE( test_preproc_defines )
{
test_config_cache& cache = test_config_cache::instance();
config test_config;
config* child = &test_config.add_child("textdomain");
(*child)["name"] = "wesnoth";
const preproc_map& test_defines = cache.get_preproc_map();
preproc_map defines_map(settup_test_preproc_map());
child = &test_config.add_child("test_key");
(*child)["define"] = "test";
// check initial state
BOOST_REQUIRE_EQUAL_COLLECTIONS(test_defines.begin(),test_defines.end(),
defines_map.begin() ,defines_map.end());
child = &test_config.add_child("test_key2");
(*child)["define"] = t_string("testing translation reset.", GETTEXT_DOMAIN);
// scoped
{
test_scoped_define test("TEST");
defines_map["TEST"] = preproc_define();
BOOST_CHECK_EQUAL((*test_config.child("test_key2"))["define"].str(), (*cache.get_config().child("test_key2"))["define"].str());
BOOST_CHECK_EQUAL_COLLECTIONS(test_defines.begin(),test_defines.end(),
defines_map.begin() ,defines_map.end());
defines_map.erase("TEST");
}
// Check scoped remove
// Change language
const language_def& original_lang = get_language();
const std::vector<language_def>& languages = get_languages();
BOOST_CHECK_MESSAGE(languages.size()>0, "No languages found!");
std::vector<language_def>::const_iterator German = std::find_if(languages.begin(),
languages.end(),
match_german); // Using German because the most active translation
BOOST_REQUIRE_MESSAGE(German != languages.end() && German->available(), "German translation not found");
::set_language(*German);
cache.reload_translations();
BOOST_CHECK_EQUAL_COLLECTIONS(test_defines.begin(),test_defines.end(),
defines_map.begin() ,defines_map.end());
BOOST_CHECK_MESSAGE((*test_config.child("test_key2"))["define"].str() != (*cache.get_config().child("test_key2"))["define"].str(), "Translation reset failed to make test string different!");
(*child)["define"].reset_translation();
// Manual add define
cache.add_define("TEST");
defines_map["TEST"] = preproc_define();
BOOST_CHECK_EQUAL_COLLECTIONS(test_defines.begin(),test_defines.end(),
defines_map.begin() ,defines_map.end());
BOOST_CHECK_EQUAL(test_config, cache.get_config());
BOOST_CHECK_EQUAL((*test_config.child("test_key2"))["define"].str(), (*cache.get_config().child("test_key2"))["define"].str());
set_language(original_lang);
// Manual remove define
cache.remove_define("TEST");
defines_map.erase("TEST");
BOOST_CHECK_EQUAL_COLLECTIONS(test_defines.begin(),test_defines.end(),
defines_map.begin() ,defines_map.end());
}
/* vim: set ts=4 sw=4: */
BOOST_AUTO_TEST_SUITE_END()