mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-07 10:33:25 +00:00

The only real change is to empty_vconfig(), which no longer requires the overhead of the vconfig making a copy of the empty config.
766 lines
21 KiB
C++
766 lines
21 KiB
C++
/*
|
|
Copyright (C) 2003 by David White <dave@whitevine.net>
|
|
Copyright (C) 2005 - 2013 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 as published by
|
|
the Free Software Foundation; either version 2 of the License, 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
|
|
* Manage WML-variables.
|
|
*/
|
|
|
|
#include "global.hpp"
|
|
|
|
#include "variable.hpp"
|
|
|
|
#include "formula_string_utils.hpp"
|
|
#include "gamestatus.hpp"
|
|
#include "log.hpp"
|
|
#include "resources.hpp"
|
|
#include "unit.hpp"
|
|
#include "unit_map.hpp"
|
|
#include "team.hpp"
|
|
|
|
#include <boost/foreach.hpp>
|
|
#include <boost/variant/static_visitor.hpp>
|
|
|
|
static lg::log_domain log_engine("engine");
|
|
#define LOG_NG LOG_STREAM(info, log_engine)
|
|
#define WRN_NG LOG_STREAM(warn, log_engine)
|
|
#define ERR_NG LOG_STREAM(err, log_engine)
|
|
|
|
namespace
|
|
{
|
|
/**
|
|
* @todo FIXME: the variable repository should be
|
|
* a class of variable.hpp, and not the game_state.
|
|
*/
|
|
#define repos (resources::state_of_game)
|
|
|
|
// keeps track of insert_tag variables used by get_parsed_config
|
|
std::set<std::string> vconfig_recursion;
|
|
|
|
/**
|
|
* config_cache is a map to track temp storage of inserted tags on the heap.
|
|
* If an event is spawned from a variable or any ActionWML from a volatile
|
|
* source is to be executed safely then its memory should be managed by vconfig
|
|
*/
|
|
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;
|
|
|
|
|
|
struct compare_str_ptr {
|
|
bool operator()(const std::string* s1, const std::string* s2) const
|
|
{
|
|
return (*s1) < (*s2);
|
|
}
|
|
};
|
|
|
|
class hash_memory_manager {
|
|
public:
|
|
hash_memory_manager() :
|
|
mem_()
|
|
{
|
|
}
|
|
|
|
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_;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructor from a config.
|
|
* @param[in] is_volatile Controls whether or not the vconfig makes a copy of @a cfg.
|
|
* If @a cfg might be destroyed before this vconfig is, then
|
|
* is_volatile must be set to true.
|
|
* If @a cfg will be valid for the life of this vconfig, then
|
|
* setting is_volatile to false saves some overhead.
|
|
*/
|
|
vconfig::vconfig(const config &cfg, bool is_volatile) :
|
|
cfg_(&cfg), cache_key_(is_volatile ? &cfg : NULL)
|
|
{
|
|
if(is_volatile) {
|
|
increment_config_usage(cache_key_);
|
|
if (cache_key_ != &cfg) {
|
|
//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::empty_vconfig()
|
|
{
|
|
static const config empty_config;
|
|
return vconfig(empty_config, false);
|
|
}
|
|
|
|
vconfig vconfig::unconstructed_vconfig()
|
|
{
|
|
return vconfig();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
const config vconfig::get_parsed_config() const
|
|
{
|
|
config res;
|
|
|
|
BOOST_FOREACH(const config::attribute &i, cfg_->attribute_range()) {
|
|
res[i.first] = expand(i.first);
|
|
}
|
|
|
|
BOOST_FOREACH(const config::any_child &child, cfg_->all_children_range())
|
|
{
|
|
if (child.key == "insert_tag") {
|
|
vconfig insert_cfg(child.cfg);
|
|
const t_string& name = insert_cfg["name"];
|
|
const t_string& vname = insert_cfg["variable"];
|
|
if(!vconfig_recursion.insert(vname).second) {
|
|
throw recursion_error("vconfig::get_parsed_config() infinite recursion detected, aborting");
|
|
}
|
|
try {
|
|
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());
|
|
}
|
|
}
|
|
vconfig_recursion.erase(vname);
|
|
} catch(recursion_error &err) {
|
|
vconfig_recursion.erase(vname);
|
|
WRN_NG << err.message << std::endl;
|
|
if(vconfig_recursion.empty()) {
|
|
res.add_child("insert_tag", insert_cfg.get_config());
|
|
} else {
|
|
// throw to the top [insert_tag] which started the recursion
|
|
throw;
|
|
}
|
|
}
|
|
} else {
|
|
res.add_child(child.key, vconfig(child.cfg).get_parsed_config());
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
vconfig::child_list vconfig::get_children(const std::string& key) const
|
|
{
|
|
vconfig::child_list res;
|
|
|
|
BOOST_FOREACH(const config::any_child &child, cfg_->all_children_range())
|
|
{
|
|
if (child.key == key) {
|
|
res.push_back(vconfig(&child.cfg, cache_key_));
|
|
} else if (child.key == "insert_tag") {
|
|
vconfig insert_cfg(child.cfg);
|
|
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(empty_vconfig());
|
|
} 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(empty_vconfig());
|
|
}
|
|
while(range.first != range.second) {
|
|
config *cp = &*range.first++;
|
|
res.push_back(vconfig(cp, cp));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Returns a child of *this whose key is @a key.
|
|
* If no such child exists, returns an unconstructed vconfig (use null() to test
|
|
* for this).
|
|
*/
|
|
vconfig vconfig::child(const std::string& key) const
|
|
{
|
|
if (const config &natural = cfg_->child(key)) {
|
|
return vconfig(&natural, cache_key_);
|
|
}
|
|
BOOST_FOREACH(const config &ins, cfg_->child_range("insert_tag"))
|
|
{
|
|
vconfig insert_cfg(ins);
|
|
if(insert_cfg["name"] == key) {
|
|
variable_info vinfo(insert_cfg["variable"], false, variable_info::TYPE_CONTAINER);
|
|
if(!vinfo.is_valid) {
|
|
return empty_vconfig();
|
|
}
|
|
config * cp = &(vinfo.as_container());
|
|
return vconfig(cp, cp);
|
|
}
|
|
}
|
|
return unconstructed_vconfig();
|
|
}
|
|
|
|
/**
|
|
* Returns whether or not *this has a child whose key is @a key.
|
|
*/
|
|
bool vconfig::has_child(const std::string& key) const
|
|
{
|
|
if (cfg_->child(key)) {
|
|
return true;
|
|
}
|
|
BOOST_FOREACH(const config &ins, cfg_->child_range("insert_tag"))
|
|
{
|
|
vconfig insert_cfg(ins);
|
|
if(insert_cfg["name"] == key) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
namespace {
|
|
struct vconfig_expand_visitor : boost::static_visitor<void>
|
|
{
|
|
config::attribute_value &result;
|
|
vconfig_expand_visitor(config::attribute_value &r): result(r) {}
|
|
template<typename T> void operator()(T const &) const {}
|
|
void operator()(const std::string &s) const
|
|
{
|
|
result = utils::interpolate_variables_into_string(s, *(resources::gamedata));
|
|
}
|
|
void operator()(const t_string &s) const
|
|
{
|
|
result = utils::interpolate_variables_into_tstring(s, *(resources::gamedata));
|
|
}
|
|
};
|
|
}//unnamed namespace
|
|
|
|
config::attribute_value vconfig::expand(const std::string &key) const
|
|
{
|
|
config::attribute_value val = (*cfg_)[key];
|
|
if (repos)
|
|
val.apply_visitor(vconfig_expand_visitor(val));
|
|
return val;
|
|
}
|
|
|
|
vconfig::all_children_iterator::all_children_iterator(const Itor &i, const config *cache_key)
|
|
: i_(i), inner_index_(0), cache_key_(cache_key)
|
|
{
|
|
}
|
|
|
|
vconfig::all_children_iterator& vconfig::all_children_iterator::operator++()
|
|
{
|
|
if (inner_index_ >= 0 && i_->key == "insert_tag")
|
|
{
|
|
variable_info vinfo(vconfig(i_->cfg)["variable"], false, variable_info::TYPE_CONTAINER);
|
|
if(vinfo.is_valid && !vinfo.explicit_index) {
|
|
variable_info::array_range range = vinfo.as_array();
|
|
if (++inner_index_ < std::distance(range.first, range.second)) {
|
|
return *this;
|
|
}
|
|
inner_index_ = 0;
|
|
}
|
|
}
|
|
++i_;
|
|
return *this;
|
|
}
|
|
|
|
vconfig::all_children_iterator vconfig::all_children_iterator::operator++(int)
|
|
{
|
|
vconfig::all_children_iterator i = *this;
|
|
this->operator++();
|
|
return i;
|
|
}
|
|
|
|
vconfig::all_children_iterator::reference vconfig::all_children_iterator::operator*() const
|
|
{
|
|
return value_type(get_key(), get_child());
|
|
}
|
|
|
|
vconfig::all_children_iterator::pointer vconfig::all_children_iterator::operator->() const
|
|
{
|
|
pointer_proxy p = { value_type(get_key(), get_child()) };
|
|
return p;
|
|
}
|
|
|
|
|
|
std::string vconfig::all_children_iterator::get_key() const
|
|
{
|
|
const std::string &key = i_->key;
|
|
if (inner_index_ >= 0 && key == "insert_tag") {
|
|
return vconfig(i_->cfg)["name"];
|
|
}
|
|
return key;
|
|
}
|
|
|
|
vconfig vconfig::all_children_iterator::get_child() const
|
|
{
|
|
if (inner_index_ >= 0 && i_->key == "insert_tag")
|
|
{
|
|
config * cp;
|
|
variable_info vinfo(vconfig(i_->cfg)["variable"], false, variable_info::TYPE_CONTAINER);
|
|
if(!vinfo.is_valid) {
|
|
return empty_vconfig();
|
|
} else if(inner_index_ == 0) {
|
|
cp = &(vinfo.as_container());
|
|
return vconfig(cp, cp);
|
|
}
|
|
variable_info::array_range r = vinfo.as_array();
|
|
std::advance(r.first, inner_index_);
|
|
cp = &*r.first;
|
|
return vconfig(cp, cp);
|
|
}
|
|
return vconfig(&i_->cfg, cache_key_);
|
|
}
|
|
|
|
bool vconfig::all_children_iterator::operator==(const 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(), cache_key_);
|
|
}
|
|
|
|
vconfig::all_children_iterator vconfig::ordered_end() const
|
|
{
|
|
return all_children_iterator(cfg_->ordered_end(), cache_key_);
|
|
}
|
|
|
|
namespace variable
|
|
{
|
|
manager::~manager()
|
|
{
|
|
hash_memory.clear();
|
|
}
|
|
}
|
|
|
|
scoped_wml_variable::scoped_wml_variable(const std::string& var_name) :
|
|
previous_val_(),
|
|
var_name_(var_name),
|
|
activated_(false)
|
|
{
|
|
if (resources::gamedata)
|
|
resources::gamedata->scoped_variables.push_back(this);
|
|
}
|
|
|
|
config &scoped_wml_variable::store(const config &var_value)
|
|
{
|
|
BOOST_FOREACH(const config &i, resources::gamedata->get_variables().child_range(var_name_)) {
|
|
previous_val_.add_child(var_name_, i);
|
|
}
|
|
resources::gamedata->clear_variable_cfg(var_name_);
|
|
config &res = resources::gamedata->add_variable_cfg(var_name_, var_value);
|
|
LOG_NG << "scoped_wml_variable: var_name \"" << var_name_ << "\" has been auto-stored.\n";
|
|
activated_ = true;
|
|
return res;
|
|
}
|
|
|
|
scoped_wml_variable::~scoped_wml_variable()
|
|
{
|
|
if(activated_) {
|
|
resources::gamedata->clear_variable_cfg(var_name_);
|
|
BOOST_FOREACH(const config &i, previous_val_.child_range(var_name_)) {
|
|
resources::gamedata->add_variable_cfg(var_name_, i);
|
|
}
|
|
LOG_NG << "scoped_wml_variable: var_name \"" << var_name_ << "\" has been reverted.\n";
|
|
}
|
|
if (resources::gamedata) {
|
|
assert(resources::gamedata->scoped_variables.back() == this);
|
|
resources::gamedata->scoped_variables.pop_back();
|
|
}
|
|
}
|
|
|
|
void scoped_xy_unit::activate()
|
|
{
|
|
map_location loc = map_location(x_, y_);
|
|
unit_map::const_iterator itor = umap_.find(loc);
|
|
if(itor != umap_.end()) {
|
|
config &tmp_cfg = store();
|
|
itor->write(tmp_cfg);
|
|
tmp_cfg["x"] = x_ + 1;
|
|
tmp_cfg["y"] = y_ + 1;
|
|
LOG_NG << "auto-storing $" << name() << " at (" << loc << ")\n";
|
|
} else {
|
|
ERR_NG << "failed to auto-store $" << name() << " at (" << loc << ")\n";
|
|
}
|
|
}
|
|
|
|
void scoped_weapon_info::activate()
|
|
{
|
|
if (data_) {
|
|
store(data_);
|
|
}
|
|
}
|
|
|
|
void scoped_recall_unit::activate()
|
|
{
|
|
const std::vector<team>& teams = teams_manager::get_teams();
|
|
std::vector<team>::const_iterator team_it;
|
|
for (team_it = teams.begin(); team_it != teams.end(); ++team_it) {
|
|
if (team_it->save_id() == player_ )
|
|
break;
|
|
}
|
|
|
|
if(team_it != teams.end()) {
|
|
if(team_it->recall_list().size() > recall_index_) {
|
|
config &tmp_cfg = store();
|
|
team_it->recall_list()[recall_index_].write(tmp_cfg);
|
|
tmp_cfg["x"] = "recall";
|
|
tmp_cfg["y"] = "recall";
|
|
LOG_NG << "auto-storing $" << name() << " for player: " << player_
|
|
<< " at recall index: " << recall_index_ << '\n';
|
|
} 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 = resources::gamedata->scoped_variables.rbegin(); rit != resources::gamedata->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),
|
|
key(),
|
|
explicit_index(false),
|
|
index(0),
|
|
vars(NULL)
|
|
{
|
|
assert(repos != NULL);
|
|
activate_scope_variable(varname);
|
|
|
|
vars = &resources::gamedata->variables_;
|
|
key = varname;
|
|
std::string::const_iterator itor = std::find(key.begin(),key.end(),'.');
|
|
int dot_index = key.find('.');
|
|
|
|
bool force_length = false;
|
|
// 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->child_count(element);
|
|
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(varname.length() >= 7 && varname.substr(varname.length()-7) == ".length") {
|
|
// require '.' to avoid matching suffixes -> requires varname over key to always find length
|
|
// return length 0 for non-existent WML array (handled below)
|
|
force_length = true;
|
|
} else {
|
|
WRN_NG << "variable_info: retrieving member of non-existent WML container, "
|
|
<< varname << std::endl;
|
|
return;
|
|
}
|
|
}
|
|
if((!inner_explicit_index && key == "length") || force_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 || resources::gamedata->temporaries_.child(varname);
|
|
break;
|
|
case variable_info::TYPE_SCALAR:
|
|
default:
|
|
// Store the length of the array as a temporary variable
|
|
resources::gamedata->temporaries_[varname] = int(size);
|
|
is_valid = true;
|
|
break;
|
|
}
|
|
key = varname;
|
|
vars = &resources::gamedata->temporaries_;
|
|
return;
|
|
}
|
|
|
|
vars = &vars->child(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->child_count(key);
|
|
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->child(key, index);
|
|
key = "__array";
|
|
is_valid = force_valid || vars->child(key);
|
|
break;
|
|
case variable_info::TYPE_SCALAR:
|
|
vars = &vars->child(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;
|
|
}
|
|
if (force_valid) {
|
|
WRN_NG << "variable_info: using explicitly indexed "
|
|
"container as wrong WML type, " << varname << '\n';
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
config::attribute_value &variable_info::as_scalar()
|
|
{
|
|
assert(is_valid);
|
|
return (*vars)[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->child(key, index);
|
|
}
|
|
if (config &temp = vars->child(key)) {
|
|
// 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);
|
|
}
|