Refactor handling of unit animation progressive_* helper classes

This majorly reduces code duplication. Thanks to @jyrkive for help.
This commit is contained in:
Charles Dang 2017-03-02 19:56:30 +11:00
parent 42f087abb3
commit 7d5acc92ce
4 changed files with 232 additions and 298 deletions

View file

@ -1096,6 +1096,7 @@
<Unit filename="../../src/units/formula_manager.hpp" />
<Unit filename="../../src/units/frame.cpp" />
<Unit filename="../../src/units/frame.hpp" />
<Unit filename="../../src/units/frame_private.hpp" />
<Unit filename="../../src/units/helper.cpp" />
<Unit filename="../../src/units/helper.hpp" />
<Unit filename="../../src/units/id.cpp" />

View file

@ -22,233 +22,6 @@
static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
template<typename T>
progressive_base<T>::progressive_base(const std::string& data, int duration)
: data_()
, input_(data)
{
int split_flag = utils::REMOVE_EMPTY; // useless to strip spaces
const std::vector<std::string> comma_split = utils::split(data,',',split_flag);
const int time_chunk = std::max<int>(1, duration / std::max<int>(comma_split.size(),1));
for(const auto& entry : comma_split) {
std::vector<std::string> colon_split = utils::split(entry, ':', split_flag);
int time = 0;
try {
time = (colon_split.size() > 1) ? std::stoi(colon_split[1]) : time_chunk;
} catch(std::invalid_argument) {
ERR_NG << "Invalid time in unit animation: " << colon_split[1] << "\n";
}
try {
std::vector<std::string> range = utils::split(colon_split[0],'~',split_flag);
T range0 = lexical_cast<T>(range[0]);
T range1 = (range.size() > 1) ? lexical_cast<T>(range[1]) : range0;
data_.push_back({{range0, range1}, time});
} catch(bad_lexical_cast) {}
}
}
template<typename T>
const T progressive_base<T>::get_current_element(int current_time, T default_val) const
{
if(data_.empty()) {
return default_val;
}
int time = 0;
unsigned int sub_halo = 0;
int searched_time = current_time;
if(searched_time < 0) searched_time = 0;
if(searched_time > duration()) searched_time = duration();
while(time < searched_time && sub_halo < data_.size()) {
time += data_[sub_halo].second;
++sub_halo;
}
if(sub_halo != 0) {
sub_halo--;
time -= data_[sub_halo].second;
}
const T first = data_[sub_halo].first.first;
const T second = data_[sub_halo].first.second;
return T((
static_cast<double>(searched_time - time) /
static_cast<double>(data_[sub_halo].second)
) * (second - first) + first);
}
template<typename T>
int progressive_base<T>::duration() const
{
int total = 0;
for(const auto& entry : data_) {
total += entry.second;
}
return total;
}
template<typename T>
bool progressive_base<T>::does_not_change() const
{
return data_.empty() || (data_.size() == 1 && data_[0].first.first == data_[0].first.second);
}
progressive_string::progressive_string(const std::string& data,int duration)
: data_()
, input_(data)
{
const std::vector<std::string> first_pass = utils::square_parenthetical_split(data);
int time_chunk = std::max<int>(duration, 1);
if(duration > 1 && !first_pass.empty()) {
// If duration specified, divide evenly the time for items with unspecified times
int total_specified_time = 0;
for(const std::string& fp_string : first_pass) {
std::vector<std::string> second_pass = utils::split(fp_string, ':');
if(second_pass.size() > 1) {
try {
total_specified_time += std::stoi(second_pass[1]);
} catch(std::invalid_argument) {
ERR_NG << "Invalid time in unit animation: " << second_pass[1] << "\n";
}
}
}
time_chunk = std::max<int>((duration - total_specified_time) / first_pass.size(), 1);
}
for(const std::string& fp_string : first_pass) {
std::vector<std::string> second_pass = utils::split(fp_string, ':');
if(second_pass.size() > 1) {
try {
data_.push_back({std::move(second_pass[0]), std::stoi(second_pass[1])});
} catch(std::invalid_argument) {
ERR_NG << "Invalid time in unit animation: " << second_pass[1] << "\n";
}
} else {
data_.push_back({std::move(second_pass[0]) ,time_chunk});
}
}
}
int progressive_string::duration() const
{
int total = 0;
for(const auto& entry : data_) {
total += entry.second;
}
return total;
}
progressive_image::progressive_image(const std::string& data,int duration)
: data_()
, input_(data)
{
const std::vector<std::string> first_pass = utils::square_parenthetical_split(data);
int time_chunk = std::max<int>(duration, 1);
if(duration > 1 && !first_pass.empty() ) {
// If duration specified, divide evenly the time for images with unspecified times
int total_specified_time = 0;
for(const std::string& fp_string : first_pass) {
std::vector<std::string> second_pass = utils::split(fp_string, ':');
if(second_pass.size() > 1) {
try {
total_specified_time += std::stoi(second_pass[1]);
} catch(std::invalid_argument) {
ERR_NG << "Invalid time in unit animation: " << second_pass[1] << "\n";
}
}
}
time_chunk = std::max<int>((duration - total_specified_time) / first_pass.size(), 1);
}
for(const std::string& fp_string : first_pass) {
std::vector<std::string> second_pass = utils::split(fp_string, ':');
if(second_pass.size() > 1) {
try {
data_.push_back({std::move(second_pass[0]), std::stoi(second_pass[1])});
} catch(std::invalid_argument) {
ERR_NG << "Invalid time in unit animation: " << second_pass[1] << "\n";
}
} else {
data_.push_back({std::move(second_pass[0]), time_chunk});
}
}
}
int progressive_image::duration() const
{
int total = 0;
for(const auto& entry : data_) {
total += entry.second;
}
return total;
}
const image::locator empty_image;
const image::locator& progressive_image::get_current_element(int current_time) const
{
if(data_.empty()) {
return empty_image;
}
int time = 0;
unsigned int sub_image = 0;
while(time < current_time && sub_image < data_.size()) {
time += data_[sub_image].second;
++sub_image;
}
if(sub_image) {
sub_image--;
}
return data_[sub_image].first;
}
static const std::string empty_string;
const std::string& progressive_string::get_current_element(int current_time) const
{
if(data_.empty()) {
return empty_string;
}
int time = 0;
unsigned int sub_halo = 0;
while(time < current_time && sub_halo < data_.size()) {
time += data_[sub_halo].second;
++sub_halo;
}
if(sub_halo) {
sub_halo--;
}
return data_[sub_halo].first;
}
// Force compilation of the following template instantiations
template class progressive_base<int>;
template class progressive_base<double>;
frame_parameters::frame_parameters()
: duration(0)
, image()

View file

@ -20,6 +20,8 @@
#ifndef UNIT_FRAME_H_INCLUDED
#define UNIT_FRAME_H_INCLUDED
#include "units/frame_private.hpp"
#include "halo.hpp"
#include "image.hpp"
@ -28,77 +30,6 @@
class config;
/* FIXME: these 'progressive_' classes are all mostly equivalent except for the exact type of the
* data_ vector and the interactions with it. Need to figure how to reduce the code duplication -
* perhaps inherit from progressive_base? Though do note the string/image functions are only
* unique since they don't store a pair of pairs.
*/
template<typename T>
class progressive_base
{
public:
progressive_base(const std::string& data = "", int duration = 0);
int duration() const;
const T get_current_element(int time, T default_val = 0) const;
bool does_not_change() const;
std::string get_original() const
{
return input_;
}
private:
std::vector<std::pair<std::pair<T, T>, int>> data_;
std::string input_;
};
class progressive_string
{
public:
progressive_string(const std::string& data = "", int duration = 0);
int duration() const;
const std::string& get_current_element(int time) const;
bool does_not_change() const
{
return data_.size() <= 1;
}
std::string get_original() const
{
return input_;
}
private:
std::vector<std::pair<std::string,int>> data_;
std::string input_;
};
class progressive_image
{
public:
progressive_image(const std::string& data = "", int duration = 0);
int duration() const;
const image::locator& get_current_element(int time) const;
bool does_not_change() const
{
return data_.size() <= 1;
}
std::string get_original() const
{
return input_;
}
private:
std::vector<std::pair<image::locator,int>> data_;
std::string input_;
};
typedef progressive_base<int> progressive_int;
typedef progressive_base<double> progressive_double;
/** All parameters from a frame at a given instant */
struct frame_parameters
{

229
src/units/frame_private.hpp Normal file
View file

@ -0,0 +1,229 @@
/*
Copyright (C) 2006 - 2016 by Jeremy Rosen <jeremy.rosen@enst-bretagne.fr>
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.
*/
#ifndef UNIT_FRAME_PRIVATE_HPP_INCLUDED
#define UNIT_FRAME_PRIVATE_HPP_INCLUDED
#include "lexical_cast.hpp"
#include "serialization/string_utils.hpp"
#include "utils/general.hpp"
#include <vector>
namespace image { class locator; }
template<typename T, typename D>
class progressive_base
{
public:
using data_t = std::vector<std::pair<D, int>>;
progressive_base(const std::string& input)
: data_()
, input_(input)
{}
virtual const T get_current_element(int current_time, T default_val) const = 0;
virtual bool does_not_change() const
{
return data_.size() <= 1;
}
int duration() const
{
int total = 0;
for(const auto& entry : data_) {
total += entry.second;
}
return total;
}
std::string get_original() const
{
return input_;
}
data_t& data()
{
return data_;
}
const data_t& data() const
{
return data_;
}
private:
data_t data_;
std::string input_;
};
template<typename T>
class progressive_pair : public progressive_base<T, std::pair<T, T>>
{
public:
progressive_pair(const std::string& input = "", int duration = 0)
: progressive_base<T, std::pair<T, T>>(input)
{
auto& base_data = progressive_pair_base_type::data();
const int split_flag = utils::REMOVE_EMPTY; // useless to strip spaces
const std::vector<std::string> comma_split = utils::split(input, ',', split_flag);
const int time_chunk = std::max<int>(1, duration / std::max<int>(comma_split.size(), 1));
for(const auto& entry : comma_split) {
std::vector<std::string> colon_split = utils::split(entry, ':', split_flag);
int time = 0;
try {
time = (colon_split.size() > 1) ? std::stoi(colon_split[1]) : time_chunk;
} catch(std::invalid_argument) {
//ERR_NG << "Invalid time in unit animation: " << colon_split[1] << "\n";
}
try {
std::vector<std::string> range = utils::split(colon_split[0],'~',split_flag);
T range0 = lexical_cast<T>(range[0]);
T range1 = (range.size() > 1) ? lexical_cast<T>(range[1]) : range0;
base_data.push_back({{range0, range1}, time});
} catch(bad_lexical_cast) {}
}
}
virtual const T get_current_element(int current_time, T default_val = T()) const override
{
const auto& base_data = progressive_pair_base_type::data();
const int& base_duration = progressive_pair_base_type::duration();
if(base_data.empty()) {
return default_val;
}
int time = 0;
unsigned int i = 0;
const int searched_time = util::clamp(current_time, 0, base_duration);
while(time < searched_time && i < base_data.size()) {
time += base_data[i].second;
++i;
}
if(i != 0) {
i--;
time -= base_data[i].second;
}
const T first = base_data[i].first.first;
const T second = base_data[i].first.second;
return T((
static_cast<double>(searched_time - time) /
static_cast<double>(base_data[i].second)
) * (second - first) + first);
}
bool does_not_change() const override
{
const auto& base_data = progressive_pair_base_type::data();
return base_data.empty() || (base_data.size() == 1 && base_data[0].first.first == base_data[0].first.second);
}
private:
using progressive_pair_base_type = progressive_base<T, std::pair<T, T>>;
};
template<typename T>
class progressive_single : public progressive_base<T, T>
{
public:
progressive_single(const std::string& input = "", int duration = 0)
: progressive_base<T, T>(input)
{
auto& base_data = progressive_single_base_type::data();
const std::vector<std::string> first_pass = utils::square_parenthetical_split(input);
int time_chunk = std::max<int>(duration, 1);
if(duration > 1 && !first_pass.empty()) {
// If duration specified, divide evenly the time for items with unspecified times
int total_specified_time = 0;
for(const std::string& fp_string : first_pass) {
std::vector<std::string> second_pass = utils::split(fp_string, ':');
if(second_pass.size() > 1) {
try {
total_specified_time += std::stoi(second_pass[1]);
} catch(std::invalid_argument) {
//ERR_NG << "Invalid time in unit animation: " << second_pass[1] << "\n";
}
}
}
time_chunk = std::max<int>((duration - total_specified_time) / first_pass.size(), 1);
}
for(const std::string& fp_string : first_pass) {
std::vector<std::string> second_pass = utils::split(fp_string, ':');
if(second_pass.size() > 1) {
try {
base_data.push_back({std::move(second_pass[0]), std::stoi(second_pass[1])});
} catch(std::invalid_argument) {
//ERR_NG << "Invalid time in unit animation: " << second_pass[1] << "\n";
}
} else {
base_data.push_back({std::move(second_pass[0]) ,time_chunk});
}
}
}
virtual const T get_current_element(int current_time, T default_val = T()) const override
{
const auto& base_data = progressive_single_base_type::data();
if(base_data.empty()) {
return default_val;
}
int time = 0;
unsigned int i = 0;
while(time < current_time && i < base_data.size()) {
time += base_data[i].second;
++i;
}
// TODO: what is this for?
if(i) {
i--;
}
return base_data[i].first;
}
private:
using progressive_single_base_type = progressive_base<T, T>;
};
// Common types used by the unit frame code.
using progressive_int = progressive_pair<int>;
using progressive_double = progressive_pair<double>;
using progressive_string = progressive_single<std::string>;
using progressive_image = progressive_single<image::locator>;
#endif