The Battle for Wesnoth  1.19.7+dev
frame_private.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2024
3  by Jeremy Rosen <jeremy.rosen@enst-bretagne.fr>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #pragma once
17 
18 #include "lexical_cast.hpp"
20 
21 #include <chrono>
22 #include <vector>
23 
24 namespace image { class locator; }
25 using namespace std::chrono_literals;
26 
27 template<typename T, typename D>
29 {
30 public:
31  using data_t = std::vector<std::pair<D, std::chrono::milliseconds>>;
32 
33  progressive_base(const std::string& input)
34  : data_()
35  , input_(input)
36  {}
37 
38  virtual const T get_current_element(const std::chrono::milliseconds& current_time, T default_val) const = 0;
39 
40  virtual bool does_not_change() const
41  {
42  return data_.size() <= 1;
43  }
44 
45  std::chrono::milliseconds duration() const
46  {
47  std::chrono::milliseconds total{0};
48  for(const auto& entry : data_) {
49  total += entry.second;
50  }
51 
52  return total;
53  }
54 
55  std::string get_original() const
56  {
57  return input_;
58  }
59 
61  {
62  return data_;
63  }
64 
65  const data_t& data() const
66  {
67  return data_;
68  }
69 
70  virtual ~progressive_base() {}
71 
72 private:
74  std::string input_;
75 };
76 
77 template<typename T>
78 class progressive_pair : public progressive_base<T, std::pair<T, T>>
79 {
80 public:
81  progressive_pair(const std::string& input = "", const std::chrono::milliseconds& duration = std::chrono::milliseconds{0})
82  : progressive_base<T, std::pair<T, T>>(input)
83  {
84  auto& base_data = progressive_pair_base_type::data();
85 
86  const int split_flag = utils::REMOVE_EMPTY; // useless to strip spaces
87 
88  const std::vector<std::string> comma_split = utils::split(input, ',', split_flag);
89  const auto time_chunk = std::max(1ms, duration / std::max<int>(comma_split.size(), 1));
90 
91  for(const auto& entry : comma_split) {
92  std::vector<std::string> colon_split = utils::split(entry, ':', split_flag);
93  auto time = 0ms;
94 
95  try {
96  time = (colon_split.size() > 1) ? std::chrono::milliseconds{std::stoi(colon_split[1])} : time_chunk;
97  } catch(const std::invalid_argument&) {
98  //ERR_NG << "Invalid time in unit animation: " << colon_split[1];
99  }
100 
101  try {
102  std::vector<std::string> range = utils::split(colon_split[0],'~',split_flag);
103  T range0 = lexical_cast<T>(range[0]);
104  T range1 = (range.size() > 1) ? lexical_cast<T>(range[1]) : range0;
105 
106  base_data.push_back({{range0, range1}, time});
107  } catch(const bad_lexical_cast&) {}
108  }
109  }
110 
111  virtual const T get_current_element(const std::chrono::milliseconds& current_time, T default_val = T()) const override
112  {
113  const auto& base_data = progressive_pair_base_type::data();
114  const std::chrono::milliseconds& base_duration = progressive_pair_base_type::duration();
115 
116  if(base_data.empty()) {
117  return default_val;
118  }
119 
120  auto time = 0ms;
121  unsigned int i = 0;
122  const std::chrono::milliseconds searched_time = std::clamp(current_time, 0ms, base_duration);
123 
124  while(time < searched_time && i < base_data.size()) {
125  time += base_data[i].second;
126  ++i;
127  }
128 
129  if(i != 0) {
130  i--;
131  time -= base_data[i].second;
132  }
133 
134  const auto [first, second] = base_data[i].first;
135  using fractional_milliseconds = std::chrono::duration<double, std::milli>;
136 
137  return T((
138  fractional_milliseconds{searched_time - time} /
139  fractional_milliseconds{base_data[i].second}
140  ) * (second - first) + first);
141  }
142 
143  bool does_not_change() const override
144  {
145  const auto& base_data = progressive_pair_base_type::data();
146  return base_data.empty() || (base_data.size() == 1 && base_data[0].first.first == base_data[0].first.second);
147  }
148 
149 private:
151 };
152 
153 template<typename T>
155 {
156 public:
157  progressive_single(const std::string& input = "", const std::chrono::milliseconds& duration = std::chrono::milliseconds{0})
158  : progressive_base<T, T>(input)
159  {
160  auto& base_data = progressive_single_base_type::data();
161 
162  const std::vector<std::string> first_pass = utils::square_parenthetical_split(input);
163  auto time_chunk = std::max(duration, 1ms);
164 
165  if(duration > 1ms && !first_pass.empty()) {
166  // If duration specified, divide evenly the time for items with unspecified times
167  auto total_specified_time = 0ms;
168 
169  for(const std::string& fp_string : first_pass) {
170  std::vector<std::string> second_pass = utils::split(fp_string, ':');
171  if(second_pass.size() > 1) {
172  try {
173  total_specified_time += std::chrono::milliseconds{std::stoi(second_pass[1])};
174  } catch(const std::invalid_argument&) {
175  //ERR_NG << "Invalid time in unit animation: " << second_pass[1];
176  }
177  }
178  }
179 
180  // Fun Fact: since size_t is unsigned, template argument deduction does not yield chrono::milliseconds,
181  // but rather duration<unsigned long long, std::milli>. That's why we explicitly cast to milliseconds.
182  std::chrono::milliseconds new_time_chunk{(duration - total_specified_time) / first_pass.size()};
183  time_chunk = std::max(new_time_chunk, 1ms);
184  }
185 
186  for(const std::string& fp_string : first_pass) {
187  std::vector<std::string> second_pass = utils::split(fp_string, ':');
188  if(second_pass.size() > 1) {
189  try {
190  std::chrono::milliseconds time{std::stoi(second_pass[1])};
191  base_data.emplace_back(std::move(second_pass[0]), time);
192  } catch(const std::invalid_argument&) {
193  //ERR_NG << "Invalid time in unit animation: " << second_pass[1];
194  }
195  } else {
196  base_data.emplace_back(std::move(second_pass[0]), time_chunk);
197  }
198  }
199  }
200 
201  virtual const T get_current_element(const std::chrono::milliseconds& current_time, T default_val = T()) const override
202  {
203  const auto& base_data = progressive_single_base_type::data();
204 
205  if(base_data.empty()) {
206  return default_val;
207  }
208 
209  auto time = 0ms;
210  unsigned int i = 0;
211 
212  while(time < current_time && i < base_data.size()) {
213  time += base_data[i].second;
214  ++i;
215  }
216 
217  // TODO: what is this for?
218  // ^ Seems that the value of i at this point is the first index whose time is *greater* than current_time,
219  // so the index needs to be adjusted to give the first whose time is less (or equal).
220  if(i) {
221  i--;
222  }
223 
224  return base_data[i].first;
225  }
226 
227 private:
229 };
230 
231 // Common types used by the unit frame code.
234 
const data_t & data() const
virtual ~progressive_base()
std::string input_
std::vector< std::pair< D, std::chrono::milliseconds > > data_t
progressive_base(const std::string &input)
virtual const T get_current_element(const std::chrono::milliseconds &current_time, T default_val) const =0
std::string get_original() const
virtual bool does_not_change() const
std::chrono::milliseconds duration() const
progressive_pair(const std::string &input="", const std::chrono::milliseconds &duration=std::chrono::milliseconds{0})
virtual const T get_current_element(const std::chrono::milliseconds &current_time, T default_val=T()) const override
bool does_not_change() const override
progressive_single(const std::string &input="", const std::chrono::milliseconds &duration=std::chrono::milliseconds{0})
virtual const T get_current_element(const std::chrono::milliseconds &current_time, T default_val=T()) const override
std::size_t i
Definition: function.cpp:1029
New lexcical_cast header.
Functions to load and save images from/to disk.
@ REMOVE_EMPTY
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition: charconv.hpp:154
std::vector< std::string > square_parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Similar to parenthetical_split, but also expands embedded square brackets.
std::vector< std::string > split(const config_attribute_value &val)
std::string_view data
Definition: picture.cpp:178
Thrown when a lexical_cast fails.