Logo Search packages:      
Sourcecode: wesnoth-1.9 version File versions  Download package

variable.cpp

Go to the documentation of this file.
/* $Id: variable.cpp 46186 2010-09-01 21:12:38Z silene $ */
/*
   Copyright (C) 2003 by David White <dave@whitevine.net>
   Copyright (C) 2005 - 2010 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 "foreach.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/variant.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.
       */
00049       #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
       */
00059       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:
            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_;
      }
}

vconfig::vconfig(const config &cfg, bool is_volatile) :
      cfg_(&cfg), cache_key_(&cfg)
{
      if(is_volatile) {
            increment_config_usage(cache_key_);
            if (cache_key_ != &cfg) {
                  //location of volatile cfg has moved
                  cfg_ = cache_key_;
            }
      } else {
            cache_key_ = NULL;
      }
}

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()
{
    return vconfig(config(), true);
}

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;

      foreach (const config::attribute &i, cfg_->attribute_range()) {
            res[i.first] = expand(i.first);
      }

      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 err;
                        }
                  }
            } 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;

      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(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
{
      if (const config &natural = cfg_->child(key)) {
            return vconfig(&natural, cache_key_);
      }
      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 vconfig(empty_config);
                  }
                  config * cp = &(vinfo.as_container());
                  return vconfig(cp, cp);
            }
      }
      return unconstructed_vconfig();
}

bool vconfig::has_child(const std::string& key) const
{
      if (cfg_->child(key)) {
            return true;
      }
      foreach (const config &ins, cfg_->child_range("insert_tag"))
      {
            vconfig insert_cfg(ins);
            if(insert_cfg["name"] == key) {
                  return true;
            }
      }
      return false;
}

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, *repos);
      }
      void operator()(const t_string &s) const
      {
            result = utils::interpolate_variables_into_tstring(s, *repos);
      }
};

config::attribute_value vconfig::expand(const std::string &key) const
{
      config::attribute_value val = (*cfg_)[key];
      if (repos)
            boost::apply_visitor(vconfig_expand_visitor(val), val.value);
      return val;
}

vconfig::all_children_iterator::all_children_iterator(const Itor &i, const config *cache_key)
: i_(i), inner_index_(0), index_offset_(0), cache_key_(cache_key)
{
}

vconfig::all_children_iterator& vconfig::all_children_iterator::operator++()
{
      if (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)) {
                        ++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;
}

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 (key == "insert_tag") {
            return vconfig(i_->cfg)["name"];
      }
      return key;
}

vconfig vconfig::all_children_iterator::get_child() const
{
      if (i_->key == "insert_tag")
      {
            config * cp;
            variable_info vinfo(vconfig(i_->cfg)["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);
            }
            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_;
}

00451 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)
{
      repos->scoped_variables.push_back(this);
}

config &scoped_wml_variable::store(const config &var_value)
{
      foreach (const config &i, repos->get_variables().child_range(var_name_)) {
            previous_val_.add_child(var_name_, i);
      }
      repos->clear_variable_cfg(var_name_);
      config &res = repos->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_) {
            repos->clear_variable_cfg(var_name_);
            foreach (const config &i, previous_val_.child_range(var_name_)) {
                  repos->add_variable_cfg(var_name_, i);
            }
            LOG_NG << "scoped_wml_variable: var_name \"" << var_name_ << "\" has been reverted.\n";
      }
      assert(repos->scoped_variables.back() == this);
      repos->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';
                  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 */
00555 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),
      key(),
      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->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(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);
                        break;
                  case variable_info::TYPE_SCALAR:
                  default:
                        // Store the length of the array as a temporary variable
                        repos->temporaries[varname] = int(size);
                        is_valid = true;
                        break;
                  }
                  key = varname;
                  vars = &repos->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;
            }
      }
}

00720 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);
}

Generated by  Doxygen 1.6.0   Back to index