wesnoth/src/variable.cpp
Patrick Parker 73a5b58d18 fixed a missing bit of code in the vconfig constructor
added a more advanced [insert_tag] usage to scenario-test.cfg

(right click on the Elvish Avenger to test it)
2008-04-22 03:13:28 +00:00

680 lines
20 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 by David White <dave@whitevine.net>
Copyright (C) 2005 - 2008 by Philippe Plantier <ayin@anathas.org>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
//! @file variable.cpp
//! Manage WML-variables.
#include "global.hpp"
#include "gamestatus.hpp"
#include "log.hpp"
#include <cassert>
#include <iostream>
#define LOG_NG LOG_STREAM(info, engine)
#define WRN_NG LOG_STREAM(warn, engine)
#define ERR_NG LOG_STREAM(err, engine)
namespace
{
//! @todo FIXME: the variable repository should be
// a class of variable.hpp, and not the game_state.
game_state* repos = NULL;
// map to track temp storage of inserted tags on the heap
std::map<config const *, int> config_cache;
// map by hash for equivalent inserted tags already in the cache
std::map<std::string const *, config const *> hash_to_cache;
// map to remember config hashes that have already been calculated
std::map<config const *, std::string const *> config_hashes;
config empty_config;
struct compare_str_ptr {
bool operator()(const std::string* s1, const std::string* s2) const
{
return (*s1) < (*s2);
}
};
class hash_memory_manager {
public:
const std::string *find(const std::string& str) const {
std::set<std::string const*, compare_str_ptr>::const_iterator itor = mem_.lower_bound(&str);
if(itor == mem_.end() || **itor != str) {
return NULL;
}
return *itor;
}
void insert(const std::string *newhash) {
mem_.insert(newhash);
}
void clear() {
hash_to_cache.clear();
config_hashes.clear();
std::set<std::string const*, compare_str_ptr>::iterator mem_it,
mem_end = mem_.end();
for(mem_it = mem_.begin(); mem_it != mem_end; ++mem_it) {
delete *mem_it;
}
mem_.clear();
}
~hash_memory_manager() {
clear();
}
private:
std::set<std::string const*, compare_str_ptr> mem_;
};
hash_memory_manager hash_memory;
}
static const std::string* get_hash_of(const config* cp) {
//first see if the memory of a constant config hash exists
std::map<config const *, std::string const *>::iterator ch_it = config_hashes.find(cp);
if(ch_it != config_hashes.end()) {
return ch_it->second;
}
//next see if an equivalent hash string has been memorized
const std::string & temp_hash = cp->hash();
std::string const* find_hash = hash_memory.find(temp_hash);
if(find_hash != NULL) {
return find_hash;
}
//finally, we just allocate a new hash string to memory
std::string* new_hash = new std::string(temp_hash);
hash_memory.insert(new_hash);
//do not insert into config_hashes (may be a variable config)
return new_hash;
}
static void increment_config_usage(const config*& key) {
if(key == NULL) return;
std::map<config const *, int>::iterator this_usage = config_cache.find(key);
if(this_usage != config_cache.end()) {
++this_usage->second;
return;
}
const std::string *hash = get_hash_of(key);
const config *& cfg_store = hash_to_cache[hash];
if(cfg_store == NULL || (key != cfg_store && *key != *cfg_store)) {
// this is a new volatile config: allocate some memory & update key
key = new config(*key);
// remember this cache to prevent an equivalent one from being created
cfg_store = key;
// since the config is now constant, we can safely memorize the hash
config_hashes[key] = hash;
} else {
// swap the key with an equivalent or equal one in the cache
key = cfg_store;
}
++(config_cache[key]);
}
static void decrement_config_usage(const config* key) {
if(key == NULL) return;
std::map<config const *, int>::iterator this_usage = config_cache.find(key);
assert(this_usage != config_cache.end());
if(--(this_usage->second) == 0) {
config_cache.erase(this_usage);
if(config_cache.empty()) {
hash_memory.clear();
} else {
if(!hash_to_cache.empty()) {
hash_to_cache.erase(get_hash_of(key));
}
config_hashes.erase(key);
}
delete key;
}
}
vconfig::vconfig() :
cfg_(NULL), cache_key_(NULL)
{
}
vconfig::vconfig(const config* cfg, const config * cache_key) :
cfg_(cfg), cache_key_(cache_key)
{
increment_config_usage(cache_key_);
if(cache_key_ != cache_key) {
//location of volatile cfg has moved
cfg_ = cache_key_;
}
}
vconfig::vconfig(const vconfig& v) :
cfg_(v.cfg_), cache_key_(v.cache_key_)
{
increment_config_usage(cache_key_);
}
vconfig::~vconfig()
{
decrement_config_usage(cache_key_);
}
vconfig& vconfig::operator=(const vconfig cfg)
{
const config* prev_key = cache_key_;
cfg_ = cfg.cfg_;
cache_key_ = cfg.cache_key_;
increment_config_usage(cache_key_);
decrement_config_usage(prev_key);
return *this;
}
vconfig& vconfig::operator=(const config* cfg)
{
if(cfg_ != cfg) {
cfg_ = cfg;
decrement_config_usage(cache_key_);
cache_key_ = NULL;
}
return *this;
}
const config vconfig::get_parsed_config() const
{
config res;
for(string_map::const_iterator itor = cfg_->values.begin();
itor != cfg_->values.end(); ++itor)
{
res[itor->first] = expand(itor->first);
}
for(config::all_children_iterator child = cfg_->ordered_begin();
child != cfg_->ordered_end(); ++child)
{
const std::string &child_key = *(*child).first;
if(child_key == "insert_tag") {
vconfig insert_cfg(child->second);
const t_string& name = insert_cfg["name"];
const t_string& vname = insert_cfg["variable"];
if(!recursion_.insert(vname).second) {
ERR_NG << "vconfig::get_parsed_config() infinite recursion detected, aborting"
<< std::endl;
res.add_child("insert_tag", insert_cfg.get_config());
return res;
}
variable_info vinfo(vname, false, variable_info::TYPE_CONTAINER);
if(!vinfo.is_valid) {
res.add_child(name); //add empty tag
} else if(vinfo.explicit_index) {
res.add_child(name, vconfig(&(vinfo.as_container())).get_parsed_config());
} else {
variable_info::array_range range = vinfo.as_array();
if(range.first == range.second) {
res.add_child(name); //add empty tag
}
while(range.first != range.second) {
res.add_child(name, vconfig(*range.first++).get_parsed_config());
}
}
recursion_.erase(vname);
} else {
res.add_child(child_key, vconfig((*child).second).get_parsed_config());
}
}
return res;
}
vconfig::child_list vconfig::get_children(const std::string& key) const
{
vconfig::child_list res;
for(config::all_children_iterator child = cfg_->ordered_begin();
child != cfg_->ordered_end(); ++child)
{
const std::string &child_key = *(*child).first;
if(child_key == key) {
res.push_back(vconfig(child->second, cache_key_));
} else if(child_key == "insert_tag") {
vconfig insert_cfg(child->second);
if(insert_cfg["name"] == key) {
variable_info vinfo(insert_cfg["variable"], false, variable_info::TYPE_CONTAINER);
if(!vinfo.is_valid) {
//push back an empty tag
res.push_back(vconfig(&empty_config));
} else if(vinfo.explicit_index) {
config * cp = &(vinfo.as_container());
res.push_back(vconfig(cp, cp));
} else {
variable_info::array_range range = vinfo.as_array();
if(range.first == range.second) {
//push back an empty tag
res.push_back(vconfig(&empty_config));
}
while(range.first != range.second) {
config * cp = *range.first++;
res.push_back(vconfig(cp, cp));
}
}
}
}
}
return res;
}
vconfig vconfig::child(const std::string& key) const
{
const config *natural = cfg_->child(key);
if(natural)
{
return vconfig(natural, cache_key_);
}
for(config::const_child_itors chitors = cfg_->child_range("insert_tag");
chitors.first != chitors.second; ++chitors.first)
{
vconfig insert_cfg(*chitors.first);
if(insert_cfg["name"] == key) {
variable_info vinfo(insert_cfg["variable"], false, variable_info::TYPE_CONTAINER);
if(!vinfo.is_valid) {
return vconfig(&empty_config);
}
config * cp = &(vinfo.as_container());
return vconfig(cp, cp);
}
}
return vconfig();
}
bool vconfig::has_child(const std::string& key) const
{
if(cfg_->child(key) != NULL) {
return true;
}
for(config::const_child_itors chitors = cfg_->child_range("insert_tag");
chitors.first != chitors.second; ++chitors.first)
{
vconfig insert_cfg(*chitors.first);
if(insert_cfg["name"] == key) {
return true;
}
}
return false;
}
const t_string vconfig::expand(const std::string& key) const
{
const t_string& val = (*cfg_)[key];
if(repos != NULL && !val.str().empty()) {
std::string interp = utils::interpolate_variables_into_string(val.str(), *repos);
if(val.str() != interp) {
return t_string(interp);
}
}
return t_string(val);
}
vconfig::all_children_iterator::all_children_iterator(config::all_children_iterator i)
: i_(i), inner_index_(0), index_offset_(0)
{
}
vconfig::all_children_iterator& vconfig::all_children_iterator::operator++()
{
if(i_.get_key() == "insert_tag") {
variable_info vinfo(vconfig(&i_.get_child())["variable"], false, variable_info::TYPE_CONTAINER);
if(vinfo.is_valid && !vinfo.explicit_index) {
variable_info::array_range range = vinfo.as_array();
if(range.first != range.second && range.first + (++inner_index_) != range.second) {
++index_offset_;
return *this;
}
}
}
++i_;
inner_index_ = 0;
return *this;
}
vconfig::all_children_iterator vconfig::all_children_iterator::operator++(int)
{
vconfig::all_children_iterator i = *this;
this->operator++();
return i;
}
std::pair<const std::string,const vconfig> vconfig::all_children_iterator::operator*() const
{
return std::make_pair<const std::string, const vconfig>(get_key(), get_child());
}
vconfig::all_children_iterator::pointer vconfig::all_children_iterator::operator->() const
{
return pointer(new std::pair<const std::string, const vconfig>(get_key(), get_child()));
}
const std::string vconfig::all_children_iterator::get_key() const
{
const std::string& key = i_.get_key();
if(key == "insert_tag") {
return vconfig(&i_.get_child())["name"];
}
return key;
}
const vconfig vconfig::all_children_iterator::get_child() const
{
if(i_.get_key() == "insert_tag") {
config * cp;
variable_info vinfo(vconfig(&i_.get_child())["variable"], false, variable_info::TYPE_CONTAINER);
if(!vinfo.is_valid) {
return vconfig(&empty_config);
} else if(inner_index_ == 0) {
cp = &(vinfo.as_container());
return vconfig(cp, cp);
}
cp = *(vinfo.as_array().first + inner_index_);
return vconfig(cp, cp);
}
return vconfig(&i_.get_child());
}
size_t vconfig::all_children_iterator::get_index() const
{
return i_.get_index() + index_offset_;
}
bool vconfig::all_children_iterator::operator==(all_children_iterator i) const
{
return (i_ == i.i_ && inner_index_ == i.inner_index_);
}
bool vconfig::all_children_iterator::operator!=(all_children_iterator i) const
{
return (i_ != i.i_ || inner_index_ != i.inner_index_);
}
vconfig::all_children_iterator vconfig::ordered_begin() const
{
return all_children_iterator(cfg_->ordered_begin());
}
vconfig::all_children_iterator vconfig::ordered_end() const
{
return all_children_iterator(cfg_->ordered_end());
}
namespace variable
{
manager::manager(game_state* repository)
{
repos = repository;
}
manager::~manager()
{
repos = NULL;
hash_memory.clear();
}
}
scoped_wml_variable::scoped_wml_variable(const std::string& var_name)
: var_name_(var_name), activated_(false)
{
repos->scoped_variables.push_back(this);
}
void scoped_wml_variable::store(const config& var_value)
{
const config::child_list& children = repos->get_variables().get_children(var_name_);
for(config::child_list::const_iterator i = children.begin(); i != children.end(); ++i) {
previous_val_.append(**i);
}
repos->clear_variable_cfg(var_name_);
repos->add_variable_cfg(var_name_, var_value);
activated_ = true;
}
scoped_wml_variable::~scoped_wml_variable()
{
if(activated_) {
repos->clear_variable_cfg(var_name_);
config::child_list old_val =previous_val_.get_children(var_name_);
for(config::child_list::iterator j=old_val.begin(); j != old_val.end() ; j++){
repos->add_variable_cfg(var_name_,**j);
}
}
assert(repos->scoped_variables.back() == this);
repos->scoped_variables.pop_back();
}
void scoped_xy_unit::activate()
{
unit_map::const_iterator itor = umap_.find(gamemap::location(x_,y_));
if(itor != umap_.end()) {
config tmp_cfg;
itor->second.write(tmp_cfg);
tmp_cfg["x"] = lexical_cast<std::string,int>(x_ + 1);
tmp_cfg["y"] = lexical_cast<std::string,int>(y_ + 1);
store(tmp_cfg);
} else {
ERR_NG << "failed to auto-store $" << name() << " at (" << x_ << ',' << y_ << ")\n";
}
}
void scoped_recall_unit::activate()
{
player_info* const player = repos->get_player(player_);
if(player != NULL) {
if(player->available_units.size() > recall_index_) {
config tmp_cfg;
player->available_units[recall_index_].write(tmp_cfg);
tmp_cfg["x"] = "recall";
tmp_cfg["y"] = "recall";
store(tmp_cfg);
} else {
ERR_NG << "failed to auto-store $" << name() << " for player: " << player_
<< " at recall index: " << recall_index_ << '\n';
}
} else {
ERR_NG << "failed to auto-store $" << name() << " for player: " << player_ << '\n';
}
}
namespace {
bool recursive_activation = false;
//! Turns on any auto-stored variables
void activate_scope_variable(std::string var_name)
{
if(recursive_activation)
return;
const std::string::iterator itor = std::find(var_name.begin(),var_name.end(),'.');
if(itor != var_name.end()) {
var_name.erase(itor, var_name.end());
}
std::vector<scoped_wml_variable*>::reverse_iterator rit;
for(rit = repos->scoped_variables.rbegin(); rit != repos->scoped_variables.rend(); ++rit) {
if((**rit).name() == var_name) {
recursive_activation = true;
if(!(**rit).activated()) {
(**rit).activate();
}
recursive_activation = false;
break;
}
}
}
} // end anonymous namespace
variable_info::variable_info(const std::string& varname, bool force_valid, TYPE validation_type)
: vartype(validation_type), is_valid(false), explicit_index(false), index(0), vars(NULL)
{
assert(repos != NULL);
activate_scope_variable(varname);
vars = &repos->variables;
key = varname;
std::string::const_iterator itor = std::find(key.begin(),key.end(),'.');
int dot_index = key.find('.');
// example varname = "unit_store.modifications.trait[0]"
while(itor != key.end()) { // subvar access
std::string element=key.substr(0,dot_index);
key = key.substr(dot_index+1);
size_t inner_index = 0;
const std::string::iterator index_start = std::find(element.begin(),element.end(),'[');
const bool inner_explicit_index = index_start != element.end();
if(inner_explicit_index) {
const std::string::iterator index_end = std::find(index_start,element.end(),']');
const std::string index_str(index_start+1,index_end);
inner_index = static_cast<size_t>(lexical_cast_default<int>(index_str));
if(inner_index > game_config::max_loop) {
ERR_NG << "variable_info: index greater than " << game_config::max_loop
<< ", truncated\n";
inner_index = game_config::max_loop;
}
element = std::string(element.begin(),index_start);
}
size_t size = vars->get_children(element).size();
if(size <= inner_index) {
if(force_valid) {
// Add elements to the array until the requested size is attained
if(inner_explicit_index || key != "length") {
for(; size <= inner_index; ++size) {
vars->add_child(element);
}
}
} else if(inner_explicit_index) {
WRN_NG << "variable_info: invalid WML array index, "
<< varname << std::endl;
return;
} else if(key != "length") {
WRN_NG << "variable_info: retrieving member of non-existant WML container, "
<< varname << std::endl;
return;
} //else return length 0 for non-existant WML array (handled below)
}
if(!inner_explicit_index && key == "length") {
switch(vartype) {
case variable_info::TYPE_ARRAY:
case variable_info::TYPE_CONTAINER:
WRN_NG << "variable_info: using reserved WML variable as wrong type, "
<< varname << std::endl;
is_valid = force_valid || repos->temporaries.child(varname) != NULL;
break;
case variable_info::TYPE_SCALAR:
default:
// Store the length of the array as a temporary variable
repos->temporaries[varname] = lexical_cast<std::string>(size);
is_valid = true;
break;
}
key = varname;
vars = &repos->temporaries;
return;
}
//std::cerr << "Entering " << element << "[" << inner_index << "] of [" << vars->get_children(element).size() << "]\n";
vars = vars->get_children(element)[inner_index];
itor = std::find(key.begin(),key.end(),'.');
dot_index = key.find('.');
} // end subvar access
const std::string::iterator index_start = std::find(key.begin(),key.end(),'[');
explicit_index = index_start != key.end();
if(explicit_index) {
const std::string::iterator index_end = std::find(index_start,key.end(),']');
const std::string index_str(index_start+1,index_end);
index = static_cast<size_t>(lexical_cast_default<int>(index_str));
if(index > game_config::max_loop) {
ERR_NG << "variable_info: index greater than " << game_config::max_loop
<< ", truncated\n";
index = game_config::max_loop;
}
key = std::string(key.begin(),index_start);
size_t size = vars->get_children(key).size();
if(size <= index) {
if(!force_valid) {
WRN_NG << "variable_info: invalid WML array index, " << varname << std::endl;
return;
}
for(; size <= index; ++size) {
vars->add_child(key);
}
}
switch(vartype) {
case variable_info::TYPE_ARRAY:
vars = vars->get_children(key)[index];
key = "__array";
is_valid = force_valid || vars->child(key) != NULL;
break;
case variable_info::TYPE_SCALAR:
vars = vars->get_children(key)[index];
key = "__value";
is_valid = force_valid || vars->has_attribute(key);
break;
case variable_info::TYPE_CONTAINER:
case variable_info::TYPE_UNSPECIFIED:
default:
is_valid = true;
return;
}
WRN_NG << "variable_info: using explicitly indexed Container as wrong WML type, "
<< varname << std::endl;
explicit_index = false;
index = 0;
} else {
// Final variable is not an explicit index [...]
switch(vartype) {
case variable_info::TYPE_ARRAY:
case variable_info::TYPE_CONTAINER:
is_valid = force_valid || vars->child(key);
break;
case variable_info::TYPE_SCALAR:
is_valid = force_valid || vars->has_attribute(key);
break;
case variable_info::TYPE_UNSPECIFIED:
default:
is_valid = true;
break;
}
}
}
t_string& variable_info::as_scalar() {
assert(is_valid);
return vars->values[key];
}
config& variable_info::as_container() {
assert(is_valid);
if(explicit_index) {
// Empty data for explicit index was already created if it was needed
return *vars->get_children(key)[index];
}
config *temp = vars->child(key);
if(temp) {
// The container exists, index not specified, return index 0
return *temp;
}
// Add empty data for the new variable, since it does not exist yet
return vars->add_child(key);
}
variable_info::array_range variable_info::as_array() {
assert(is_valid);
return vars->child_range(key);
}