mirror of https://github.com/OpenTTD/OpenTTD
Compare commits
6 Commits
894e6bb087
...
6fa7dd17e3
Author | SHA1 | Date |
---|---|---|
|
6fa7dd17e3 | |
|
cd95712dd1 | |
|
821784004d | |
|
f0447d59d4 | |
|
cbdd358ae8 | |
|
2cdd50f40e |
|
@ -410,7 +410,7 @@ void VehicleCargoList::AgeCargo()
|
||||||
return (accepted && cp->first_station != current_station) ? MTA_DELIVER : MTA_KEEP;
|
return (accepted && cp->first_station != current_station) ? MTA_DELIVER : MTA_KEEP;
|
||||||
} else if (cargo_next == current_station) {
|
} else if (cargo_next == current_station) {
|
||||||
return MTA_DELIVER;
|
return MTA_DELIVER;
|
||||||
} else if (next_station.Contains(cargo_next.base())) {
|
} else if (next_station.Contains(cargo_next)) {
|
||||||
return MTA_KEEP;
|
return MTA_KEEP;
|
||||||
} else {
|
} else {
|
||||||
return MTA_TRANSFER;
|
return MTA_TRANSFER;
|
||||||
|
@ -470,7 +470,7 @@ bool VehicleCargoList::Stage(bool accepted, StationID current_station, StationID
|
||||||
new_shares.ChangeShare(current_station, INT_MIN);
|
new_shares.ChangeShare(current_station, INT_MIN);
|
||||||
StationIDStack excluded = next_station;
|
StationIDStack excluded = next_station;
|
||||||
while (!excluded.IsEmpty() && !new_shares.GetShares()->empty()) {
|
while (!excluded.IsEmpty() && !new_shares.GetShares()->empty()) {
|
||||||
new_shares.ChangeShare(StationID{excluded.Pop()}, INT_MIN);
|
new_shares.ChangeShare(excluded.Pop(), INT_MIN);
|
||||||
}
|
}
|
||||||
if (new_shares.GetShares()->empty()) {
|
if (new_shares.GetShares()->empty()) {
|
||||||
cargo_next = StationID::Invalid();
|
cargo_next = StationID::Invalid();
|
||||||
|
@ -743,7 +743,7 @@ uint StationCargoList::ShiftCargo(Taction action, StationIDStack next, bool incl
|
||||||
{
|
{
|
||||||
uint max_move = action.MaxMove();
|
uint max_move = action.MaxMove();
|
||||||
while (!next.IsEmpty()) {
|
while (!next.IsEmpty()) {
|
||||||
this->ShiftCargo(action, StationID{next.Pop()});
|
this->ShiftCargo(action, next.Pop());
|
||||||
if (action.MaxMove() == 0) break;
|
if (action.MaxMove() == 0) break;
|
||||||
}
|
}
|
||||||
if (include_invalid && action.MaxMove() > 0) {
|
if (include_invalid && action.MaxMove() > 0) {
|
||||||
|
@ -853,7 +853,7 @@ uint StationCargoList::Load(uint max_move, VehicleCargoList *dest, StationIDStac
|
||||||
*/
|
*/
|
||||||
uint StationCargoList::Reroute(uint max_move, StationCargoList *dest, StationID avoid, StationID avoid2, const GoodsEntry *ge)
|
uint StationCargoList::Reroute(uint max_move, StationCargoList *dest, StationID avoid, StationID avoid2, const GoodsEntry *ge)
|
||||||
{
|
{
|
||||||
return this->ShiftCargo(StationCargoReroute(this, dest, max_move, avoid, avoid2, ge), avoid.base(), false);
|
return this->ShiftCargo(StationCargoReroute(this, dest, max_move, avoid, avoid2, ge), avoid, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -555,7 +555,7 @@ public:
|
||||||
inline bool HasCargoFor(StationIDStack next) const
|
inline bool HasCargoFor(StationIDStack next) const
|
||||||
{
|
{
|
||||||
while (!next.IsEmpty()) {
|
while (!next.IsEmpty()) {
|
||||||
if (this->packets.find(StationID{next.Pop()}) != this->packets.end()) return true;
|
if (this->packets.find(next.Pop()) != this->packets.end()) return true;
|
||||||
}
|
}
|
||||||
/* Packets for StationID::Invalid() can go anywhere. */
|
/* Packets for StationID::Invalid() can go anywhere. */
|
||||||
return this->packets.find(StationID::Invalid()) != this->packets.end();
|
return this->packets.find(StationID::Invalid()) != this->packets.end();
|
||||||
|
|
|
@ -113,13 +113,14 @@ struct SmallStackItem {
|
||||||
* index types of the same length.
|
* index types of the same length.
|
||||||
* @tparam Titem Value type to be used.
|
* @tparam Titem Value type to be used.
|
||||||
* @tparam Tindex Index type to use for the pool.
|
* @tparam Tindex Index type to use for the pool.
|
||||||
* @tparam Tinvalid Invalid item to keep at the bottom of each stack.
|
* @tparam Tinvalid_value Value to construct invalid item to keep at the bottom of each stack.
|
||||||
* @tparam Tgrowth_step Growth step for pool.
|
* @tparam Tgrowth_step Growth step for pool.
|
||||||
* @tparam Tmax_size Maximum size for pool.
|
* @tparam Tmax_size Maximum size for pool.
|
||||||
*/
|
*/
|
||||||
template <typename Titem, typename Tindex, Titem Tinvalid, Tindex Tgrowth_step, Tindex Tmax_size>
|
template <typename Titem, typename Tindex, auto Tinvalid_value, Tindex Tgrowth_step, Tindex Tmax_size>
|
||||||
class SmallStack : public SmallStackItem<Titem, Tindex> {
|
class SmallStack : public SmallStackItem<Titem, Tindex> {
|
||||||
public:
|
public:
|
||||||
|
static constexpr Titem Tinvalid{Tinvalid_value};
|
||||||
|
|
||||||
typedef SmallStackItem<Titem, Tindex> Item;
|
typedef SmallStackItem<Titem, Tindex> Item;
|
||||||
|
|
||||||
|
|
16
src/gfx.cpp
16
src/gfx.cpp
|
@ -580,10 +580,11 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
|
||||||
|
|
||||||
const uint shadow_offset = ScaleGUITrad(1);
|
const uint shadow_offset = ScaleGUITrad(1);
|
||||||
|
|
||||||
auto draw_line = [&](const ParagraphLayouter::Line &line, bool do_shadow, int left, int min_x, int max_x, bool truncation, TextColour &last_colour) {
|
auto draw_line = [&](const ParagraphLayouter::Line &line, bool do_shadow, int left, int min_x, int max_x, bool truncation, TextColour initial_colour) {
|
||||||
const DrawPixelInfo *dpi = _cur_dpi;
|
const DrawPixelInfo *dpi = _cur_dpi;
|
||||||
int dpi_left = dpi->left;
|
int dpi_left = dpi->left;
|
||||||
int dpi_right = dpi->left + dpi->width - 1;
|
int dpi_right = dpi->left + dpi->width - 1;
|
||||||
|
TextColour last_colour = initial_colour;
|
||||||
|
|
||||||
for (int run_index = 0; run_index < line.CountRuns(); run_index++) {
|
for (int run_index = 0; run_index < line.CountRuns(); run_index++) {
|
||||||
const ParagraphLayouter::VisualRun &run = line.GetVisualRun(run_index);
|
const ParagraphLayouter::VisualRun &run = line.GetVisualRun(run_index);
|
||||||
|
@ -593,13 +594,10 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
|
||||||
|
|
||||||
FontCache *fc = f->fc;
|
FontCache *fc = f->fc;
|
||||||
TextColour colour = f->colour;
|
TextColour colour = f->colour;
|
||||||
if (colour == TC_INVALID || HasFlag(last_colour, TC_FORCED)) {
|
if (colour == TC_INVALID || HasFlag(initial_colour, TC_FORCED)) colour = initial_colour;
|
||||||
colour = last_colour;
|
bool colour_has_shadow = (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK;
|
||||||
} else {
|
|
||||||
/* Update the last colour for the truncation ellipsis. */
|
/* Update the last colour for the truncation ellipsis. */
|
||||||
last_colour = colour;
|
last_colour = colour;
|
||||||
}
|
|
||||||
bool colour_has_shadow = (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK;
|
|
||||||
if (do_shadow && (!fc->GetDrawGlyphShadow() || !colour_has_shadow)) continue;
|
if (do_shadow && (!fc->GetDrawGlyphShadow() || !colour_has_shadow)) continue;
|
||||||
SetColourRemap(do_shadow ? TC_BLACK : colour);
|
SetColourRemap(do_shadow ? TC_BLACK : colour);
|
||||||
|
|
||||||
|
@ -625,16 +623,16 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
|
||||||
GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap);
|
GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return last_colour;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Draw shadow, then foreground */
|
/* Draw shadow, then foreground */
|
||||||
for (bool do_shadow : {true, false}) {
|
for (bool do_shadow : {true, false}) {
|
||||||
TextColour last_colour = default_colour;
|
TextColour colour = draw_line(line, do_shadow, left - offset_x, min_x, max_x, truncation, default_colour);
|
||||||
draw_line(line, do_shadow, left - offset_x, min_x, max_x, truncation, last_colour);
|
|
||||||
|
|
||||||
if (truncation) {
|
if (truncation) {
|
||||||
int x = (_current_text_dir == TD_RTL) ? left : (right - truncation_width);
|
int x = (_current_text_dir == TD_RTL) ? left : (right - truncation_width);
|
||||||
draw_line(*truncation_layout->front(), do_shadow, x, INT32_MIN, INT32_MAX, false, last_colour);
|
draw_line(*truncation_layout->front(), do_shadow, x, INT32_MIN, INT32_MAX, false, colour);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
/** @file graph_gui.cpp GUI that shows performance graphs. */
|
/** @file graph_gui.cpp GUI that shows performance graphs. */
|
||||||
|
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
|
#include <ranges>
|
||||||
#include "misc/history_func.hpp"
|
#include "misc/history_func.hpp"
|
||||||
#include "graph_gui.h"
|
#include "graph_gui.h"
|
||||||
#include "window_gui.h"
|
#include "window_gui.h"
|
||||||
|
@ -174,6 +175,7 @@ protected:
|
||||||
static const int GRAPH_PAYMENT_RATE_STEPS = 20; ///< Number of steps on Payment rate graph.
|
static const int GRAPH_PAYMENT_RATE_STEPS = 20; ///< Number of steps on Payment rate graph.
|
||||||
static const int PAYMENT_GRAPH_X_STEP_DAYS = 10; ///< X-axis step label for cargo payment rates "Days in transit".
|
static const int PAYMENT_GRAPH_X_STEP_DAYS = 10; ///< X-axis step label for cargo payment rates "Days in transit".
|
||||||
static const int PAYMENT_GRAPH_X_STEP_SECONDS = 20; ///< X-axis step label for cargo payment rates "Seconds in transit".
|
static const int PAYMENT_GRAPH_X_STEP_SECONDS = 20; ///< X-axis step label for cargo payment rates "Seconds in transit".
|
||||||
|
static const int ECONOMY_YEAR_MINUTES = 12; ///< Minutes per economic year.
|
||||||
static const int ECONOMY_QUARTER_MINUTES = 3; ///< Minutes per economic quarter.
|
static const int ECONOMY_QUARTER_MINUTES = 3; ///< Minutes per economic quarter.
|
||||||
static const int ECONOMY_MONTH_MINUTES = 1; ///< Minutes per economic month.
|
static const int ECONOMY_MONTH_MINUTES = 1; ///< Minutes per economic month.
|
||||||
|
|
||||||
|
@ -182,6 +184,25 @@ protected:
|
||||||
static const int MIN_GRAPH_NUM_LINES_Y = 9; ///< Minimal number of horizontal lines to draw.
|
static const int MIN_GRAPH_NUM_LINES_Y = 9; ///< Minimal number of horizontal lines to draw.
|
||||||
static const int MIN_GRID_PIXEL_SIZE = 20; ///< Minimum distance between graph lines.
|
static const int MIN_GRID_PIXEL_SIZE = 20; ///< Minimum distance between graph lines.
|
||||||
|
|
||||||
|
struct GraphScale {
|
||||||
|
StringID label = STR_NULL;
|
||||||
|
uint8_t month_increment = 0;
|
||||||
|
int16_t x_values_increment = 0;
|
||||||
|
const HistoryRange *history_range = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline constexpr GraphScale MONTHLY_SCALE_WALLCLOCK[] = {
|
||||||
|
{STR_GRAPH_LAST_24_MINUTES_TIME_LABEL, HISTORY_MONTH.total_division, ECONOMY_MONTH_MINUTES, &HISTORY_MONTH},
|
||||||
|
{STR_GRAPH_LAST_72_MINUTES_TIME_LABEL, HISTORY_QUARTER.total_division, ECONOMY_QUARTER_MINUTES, &HISTORY_QUARTER},
|
||||||
|
{STR_GRAPH_LAST_288_MINUTES_TIME_LABEL, HISTORY_YEAR.total_division, ECONOMY_YEAR_MINUTES, &HISTORY_YEAR},
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline constexpr GraphScale MONTHLY_SCALE_CALENDAR[] = {
|
||||||
|
{STR_GRAPH_LAST_24_MONTHS, HISTORY_MONTH.total_division, ECONOMY_MONTH_MINUTES, &HISTORY_MONTH},
|
||||||
|
{STR_GRAPH_LAST_24_QUARTERS, HISTORY_QUARTER.total_division, ECONOMY_QUARTER_MINUTES, &HISTORY_QUARTER},
|
||||||
|
{STR_GRAPH_LAST_24_YEARS, HISTORY_YEAR.total_division, ECONOMY_YEAR_MINUTES, &HISTORY_YEAR},
|
||||||
|
};
|
||||||
|
|
||||||
uint64_t excluded_data = 0; ///< bitmask of datasets hidden by the player.
|
uint64_t excluded_data = 0; ///< bitmask of datasets hidden by the player.
|
||||||
uint64_t excluded_range = 0; ///< bitmask of ranges hidden by the player.
|
uint64_t excluded_range = 0; ///< bitmask of ranges hidden by the player.
|
||||||
uint64_t masked_range = 0; ///< bitmask of ranges that are not available for the current data.
|
uint64_t masked_range = 0; ///< bitmask of ranges that are not available for the current data.
|
||||||
|
@ -211,7 +232,9 @@ protected:
|
||||||
};
|
};
|
||||||
std::vector<DataSet> data{};
|
std::vector<DataSet> data{};
|
||||||
|
|
||||||
std::span<const StringID> ranges = {};
|
std::span<const StringID> ranges{};
|
||||||
|
std::span<const GraphScale> scales{};
|
||||||
|
uint8_t selected_scale = 0;
|
||||||
|
|
||||||
uint8_t highlight_data = UINT8_MAX; ///< Data set that should be highlighted, or UINT8_MAX for none.
|
uint8_t highlight_data = UINT8_MAX; ///< Data set that should be highlighted, or UINT8_MAX for none.
|
||||||
uint8_t highlight_range = UINT8_MAX; ///< Data range that should be highlighted, or UINT8_MAX for none.
|
uint8_t highlight_range = UINT8_MAX; ///< Data range that should be highlighted, or UINT8_MAX for none.
|
||||||
|
@ -617,12 +640,10 @@ protected:
|
||||||
this->SetDirty();
|
this->SetDirty();
|
||||||
}};
|
}};
|
||||||
|
|
||||||
public:
|
void UpdateMatrixSize(WidgetID widget, Dimension &size, Dimension &resize, auto labels)
|
||||||
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
|
|
||||||
{
|
{
|
||||||
switch (widget) {
|
size = {};
|
||||||
case WID_GRAPH_RANGE_MATRIX:
|
for (const StringID &str : labels) {
|
||||||
for (const StringID &str : this->ranges) {
|
|
||||||
size = maxdim(size, GetStringBoundingBox(str, FS_SMALL));
|
size = maxdim(size, GetStringBoundingBox(str, FS_SMALL));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -630,11 +651,23 @@ public:
|
||||||
size.height += WidgetDimensions::scaled.framerect.Vertical();
|
size.height += WidgetDimensions::scaled.framerect.Vertical();
|
||||||
|
|
||||||
/* Set fixed height for number of ranges. */
|
/* Set fixed height for number of ranges. */
|
||||||
size.height *= static_cast<uint>(std::size(this->ranges));
|
size.height *= static_cast<uint>(std::size(labels));
|
||||||
|
|
||||||
resize.width = 0;
|
resize.width = 0;
|
||||||
resize.height = 0;
|
resize.height = 0;
|
||||||
this->GetWidget<NWidgetCore>(WID_GRAPH_RANGE_MATRIX)->SetMatrixDimension(1, ClampTo<uint32_t>(std::size(this->ranges)));
|
this->GetWidget<NWidgetCore>(widget)->SetMatrixDimension(1, ClampTo<uint32_t>(std::size(labels)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
|
||||||
|
{
|
||||||
|
switch (widget) {
|
||||||
|
case WID_GRAPH_RANGE_MATRIX:
|
||||||
|
this->UpdateMatrixSize(widget, size, resize, this->ranges);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WID_GRAPH_SCALE_MATRIX:
|
||||||
|
this->UpdateMatrixSize(widget, size, resize, this->scales | std::views::transform(&GraphScale::label));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WID_GRAPH_GRAPH: {
|
case WID_GRAPH_GRAPH: {
|
||||||
|
@ -701,6 +734,21 @@ public:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case WID_GRAPH_SCALE_MATRIX: {
|
||||||
|
uint line_height = GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical();
|
||||||
|
uint8_t selected_month_increment = this->scales[this->selected_scale].month_increment;
|
||||||
|
Rect line = r.WithHeight(line_height);
|
||||||
|
for (const auto &scale : this->scales) {
|
||||||
|
/* Redraw frame if selected */
|
||||||
|
if (selected_month_increment == scale.month_increment) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
|
||||||
|
|
||||||
|
DrawString(line.Shrink(WidgetDimensions::scaled.framerect), scale.label, TC_BLACK, SA_CENTER, false, FS_SMALL);
|
||||||
|
|
||||||
|
line = line.Translate(0, line_height);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -722,6 +770,18 @@ public:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case WID_GRAPH_SCALE_MATRIX: {
|
||||||
|
int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
|
||||||
|
const auto &scale = this->scales[row];
|
||||||
|
if (this->selected_scale != row) {
|
||||||
|
this->selected_scale = row;
|
||||||
|
this->month_increment = scale.month_increment;
|
||||||
|
this->x_values_increment = scale.x_values_increment;
|
||||||
|
this->InvalidateData();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1646,6 +1706,13 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
||||||
this->InitializeWindow(window_number, STR_GRAPH_LAST_24_MINUTES_TIME_LABEL);
|
this->InitializeWindow(window_number, STR_GRAPH_LAST_24_MINUTES_TIME_LABEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OnInit() override
|
||||||
|
{
|
||||||
|
this->BaseCargoGraphWindow::OnInit();
|
||||||
|
|
||||||
|
this->scales = TimerGameEconomy::UsingWallclockUnits() ? MONTHLY_SCALE_WALLCLOCK : MONTHLY_SCALE_CALENDAR;
|
||||||
|
}
|
||||||
|
|
||||||
CargoTypes GetCargoTypes(WindowNumber window_number) const override
|
CargoTypes GetCargoTypes(WindowNumber window_number) const override
|
||||||
{
|
{
|
||||||
CargoTypes cargo_types{};
|
CargoTypes cargo_types{};
|
||||||
|
@ -1673,7 +1740,7 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
||||||
|
|
||||||
void UpdateStatistics(bool initialize) override
|
void UpdateStatistics(bool initialize) override
|
||||||
{
|
{
|
||||||
int mo = TimerGameEconomy::month - this->num_vert_lines;
|
int mo = (TimerGameEconomy::month / this->month_increment - this->num_vert_lines) * this->month_increment;
|
||||||
auto yr = TimerGameEconomy::year;
|
auto yr = TimerGameEconomy::year;
|
||||||
while (mo < 0) {
|
while (mo < 0) {
|
||||||
yr--;
|
yr--;
|
||||||
|
@ -1711,7 +1778,7 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
||||||
transported.dash = 2;
|
transported.dash = 2;
|
||||||
auto transported_filler = Filler{transported, &Industry::ProducedHistory::transported};
|
auto transported_filler = Filler{transported, &Industry::ProducedHistory::transported};
|
||||||
|
|
||||||
FillFromHistory<GRAPH_NUM_MONTHS>(p.history, i->valid_history, produced_filler, transported_filler);
|
FillFromHistory<GRAPH_NUM_MONTHS>(p.history, i->valid_history, *this->scales[this->selected_scale].history_range, produced_filler, transported_filler);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &a : i->accepted) {
|
for (const auto &a : i->accepted) {
|
||||||
|
@ -1735,9 +1802,9 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
||||||
auto waiting_filler = Filler{waiting, &Industry::AcceptedHistory::waiting};
|
auto waiting_filler = Filler{waiting, &Industry::AcceptedHistory::waiting};
|
||||||
|
|
||||||
if (a.history == nullptr) {
|
if (a.history == nullptr) {
|
||||||
FillFromEmpty<GRAPH_NUM_MONTHS>(i->valid_history, accepted_filler, waiting_filler);
|
FillFromEmpty<GRAPH_NUM_MONTHS>(i->valid_history, *this->scales[this->selected_scale].history_range, accepted_filler, waiting_filler);
|
||||||
} else {
|
} else {
|
||||||
FillFromHistory<GRAPH_NUM_MONTHS>(*a.history, i->valid_history, accepted_filler, waiting_filler);
|
FillFromHistory<GRAPH_NUM_MONTHS>(*a.history, i->valid_history, *this->scales[this->selected_scale].history_range, accepted_filler, waiting_filler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1758,7 +1825,7 @@ static constexpr NWidgetPart _nested_industry_production_widgets[] = {
|
||||||
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
|
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
|
||||||
NWidget(NWID_VERTICAL),
|
NWidget(NWID_VERTICAL),
|
||||||
NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
|
NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
|
||||||
NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO),
|
NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_TOGGLE_RANGE),
|
||||||
NWidget(NWID_SPACER), SetMinimalSize(0, 4),
|
NWidget(NWID_SPACER), SetMinimalSize(0, 4),
|
||||||
NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
|
NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
|
||||||
NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
|
NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
|
||||||
|
@ -1767,6 +1834,8 @@ static constexpr NWidgetPart _nested_industry_production_widgets[] = {
|
||||||
NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_MATRIX), SetFill(1, 0), SetResize(0, 2), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO), SetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR),
|
NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_MATRIX), SetFill(1, 0), SetResize(0, 2), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO), SetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR),
|
||||||
NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_GRAPH_MATRIX_SCROLLBAR),
|
NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_GRAPH_MATRIX_SCROLLBAR),
|
||||||
EndContainer(),
|
EndContainer(),
|
||||||
|
NWidget(NWID_SPACER), SetMinimalSize(0, 4),
|
||||||
|
NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_SCALE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_SELECT_SCALE),
|
||||||
NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
|
NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
|
||||||
EndContainer(),
|
EndContainer(),
|
||||||
NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
|
NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),
|
||||||
|
|
|
@ -1842,7 +1842,7 @@ static void DoCreateNewIndustry(Industry *i, TileIndex tile, IndustryType type,
|
||||||
p.history[LAST_MONTH].production += ScaleByCargoScale(p.rate * 8, false);
|
p.history[LAST_MONTH].production += ScaleByCargoScale(p.rate * 8, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateValidHistory(i->valid_history);
|
UpdateValidHistory(i->valid_history, HISTORY_YEAR, TimerGameEconomy::month);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indspec->callback_mask.Test(IndustryCallbackMask::DecideColour)) {
|
if (indspec->callback_mask.Test(IndustryCallbackMask::DecideColour)) {
|
||||||
|
@ -2494,19 +2494,38 @@ void GenerateIndustries()
|
||||||
_industry_builder.Reset();
|
_industry_builder.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
Industry::ProducedHistory SumHistory(std::span<const Industry::ProducedHistory> history)
|
||||||
|
{
|
||||||
|
uint32_t production = std::accumulate(std::begin(history), std::end(history), 0, [](uint32_t r, const auto &p) { return r + p.production; });
|
||||||
|
uint32_t transported = std::accumulate(std::begin(history), std::end(history), 0, [](uint32_t r, const auto &p) { return r + p.transported; });
|
||||||
|
auto count = std::size(history);
|
||||||
|
return {.production = ClampTo<uint16_t>(production / count), .transported = ClampTo<uint16_t>(transported / count)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
Industry::AcceptedHistory SumHistory(std::span<const Industry::AcceptedHistory> history)
|
||||||
|
{
|
||||||
|
uint32_t accepted = std::accumulate(std::begin(history), std::end(history), 0, [](uint32_t r, const auto &a) { return r + a.accepted; });
|
||||||
|
uint32_t waiting = std::accumulate(std::begin(history), std::end(history), 0, [](uint32_t r, const auto &a) { return r + a.waiting; });;
|
||||||
|
auto count = std::size(history);
|
||||||
|
return {.accepted = ClampTo<uint16_t>(accepted / count), .waiting = ClampTo<uint16_t>(waiting / count)};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monthly update of industry statistics.
|
* Monthly update of industry statistics.
|
||||||
* @param i Industry to update.
|
* @param i Industry to update.
|
||||||
*/
|
*/
|
||||||
static void UpdateIndustryStatistics(Industry *i)
|
static void UpdateIndustryStatistics(Industry *i)
|
||||||
{
|
{
|
||||||
UpdateValidHistory(i->valid_history);
|
auto month = TimerGameEconomy::month;
|
||||||
|
UpdateValidHistory(i->valid_history, HISTORY_YEAR, month);
|
||||||
|
|
||||||
for (auto &p : i->produced) {
|
for (auto &p : i->produced) {
|
||||||
if (IsValidCargoType(p.cargo)) {
|
if (IsValidCargoType(p.cargo)) {
|
||||||
if (p.history[THIS_MONTH].production != 0) i->last_prod_year = TimerGameEconomy::year;
|
if (p.history[THIS_MONTH].production != 0) i->last_prod_year = TimerGameEconomy::year;
|
||||||
|
|
||||||
RotateHistory(p.history);
|
RotateHistory(p.history, i->valid_history, HISTORY_YEAR, month);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2515,7 +2534,7 @@ static void UpdateIndustryStatistics(Industry *i)
|
||||||
if (a.history == nullptr) continue;
|
if (a.history == nullptr) continue;
|
||||||
|
|
||||||
(*a.history)[THIS_MONTH].waiting = GetAndResetAccumulatedAverage<uint16_t>(a.accumulated_waiting);
|
(*a.history)[THIS_MONTH].waiting = GetAndResetAccumulatedAverage<uint16_t>(a.accumulated_waiting);
|
||||||
RotateHistory(*a.history);
|
RotateHistory(*a.history, i->valid_history, HISTORY_YEAR, month);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -622,6 +622,14 @@ STR_GRAPH_COMPANY_VALUES_CAPTION :{WHITE}Company
|
||||||
|
|
||||||
STR_GRAPH_LAST_24_MINUTES_TIME_LABEL :{TINY_FONT}{BLACK}Last 24 minutes
|
STR_GRAPH_LAST_24_MINUTES_TIME_LABEL :{TINY_FONT}{BLACK}Last 24 minutes
|
||||||
STR_GRAPH_LAST_72_MINUTES_TIME_LABEL :{TINY_FONT}{BLACK}Last 72 minutes
|
STR_GRAPH_LAST_72_MINUTES_TIME_LABEL :{TINY_FONT}{BLACK}Last 72 minutes
|
||||||
|
STR_GRAPH_LAST_288_MINUTES_TIME_LABEL :{TINY_FONT}{BLACK}Last 288 minutes
|
||||||
|
|
||||||
|
STR_GRAPH_LAST_24_MONTHS :{TINY_FONT}{BLACK}2 years (monthly)
|
||||||
|
STR_GRAPH_LAST_24_QUARTERS :{TINY_FONT}{BLACK}6 years (quarterly)
|
||||||
|
STR_GRAPH_LAST_24_YEARS :{TINY_FONT}{BLACK}24 years (yearly)
|
||||||
|
|
||||||
|
STR_GRAPH_TOGGLE_RANGE :Toggle graph for this data range
|
||||||
|
STR_GRAPH_SELECT_SCALE :Change horizontal scale of graph
|
||||||
|
|
||||||
STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION :{WHITE}Cargo Payment Rates
|
STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION :{WHITE}Cargo Payment Rates
|
||||||
STR_GRAPH_CARGO_PAYMENT_RATES_DAYS :{TINY_FONT}{BLACK}Days in transit
|
STR_GRAPH_CARGO_PAYMENT_RATES_DAYS :{TINY_FONT}{BLACK}Days in transit
|
||||||
|
|
|
@ -42,13 +42,13 @@ LinkGraphJob::LinkGraphJob(const LinkGraph &orig) :
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erase all flows originating at a specific node.
|
* Erase all flows originating at a specific station.
|
||||||
* @param from Node to erase flows for.
|
* @param from StationID to erase flows for.
|
||||||
*/
|
*/
|
||||||
void LinkGraphJob::EraseFlows(NodeID from)
|
void LinkGraphJob::EraseFlows(StationID from)
|
||||||
{
|
{
|
||||||
for (NodeID node_id = 0; node_id < this->Size(); ++node_id) {
|
for (NodeID node_id = 0; node_id < this->Size(); ++node_id) {
|
||||||
(*this)[node_id].flows.erase(StationID{from});
|
(*this)[node_id].flows.erase(from);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ LinkGraphJob::~LinkGraphJob()
|
||||||
/* The station can have been deleted. Remove all flows originating from it then. */
|
/* The station can have been deleted. Remove all flows originating from it then. */
|
||||||
Station *st = Station::GetIfValid(from.base.station);
|
Station *st = Station::GetIfValid(from.base.station);
|
||||||
if (st == nullptr) {
|
if (st == nullptr) {
|
||||||
this->EraseFlows(node_id);
|
this->EraseFlows(from.base.station);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ LinkGraphJob::~LinkGraphJob()
|
||||||
* sure that everything is still consistent or ignore it otherwise. */
|
* sure that everything is still consistent or ignore it otherwise. */
|
||||||
GoodsEntry &ge = st->goods[this->Cargo()];
|
GoodsEntry &ge = st->goods[this->Cargo()];
|
||||||
if (ge.link_graph != this->link_graph.index || ge.node != node_id) {
|
if (ge.link_graph != this->link_graph.index || ge.node != node_id) {
|
||||||
this->EraseFlows(node_id);
|
this->EraseFlows(from.base.station);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ LinkGraphJob::~LinkGraphJob()
|
||||||
/* Delete old flows for source stations which have been deleted
|
/* Delete old flows for source stations which have been deleted
|
||||||
* from the new flows. This avoids flow cycles between old and
|
* from the new flows. This avoids flow cycles between old and
|
||||||
* new flows. */
|
* new flows. */
|
||||||
while (!erased.IsEmpty()) geflows.erase(StationID{erased.Pop()});
|
while (!erased.IsEmpty()) geflows.erase(erased.Pop());
|
||||||
} else if ((*lg)[node_id][dest_id].last_unrestricted_update == EconomyTime::INVALID_DATE) {
|
} else if ((*lg)[node_id][dest_id].last_unrestricted_update == EconomyTime::INVALID_DATE) {
|
||||||
/* Edge is fully restricted. */
|
/* Edge is fully restricted. */
|
||||||
flows.RestrictFlows(to);
|
flows.RestrictFlows(to);
|
||||||
|
|
|
@ -167,7 +167,7 @@ protected:
|
||||||
std::atomic<bool> job_completed = false; ///< Is the job still running. This is accessed by multiple threads and reads may be stale.
|
std::atomic<bool> job_completed = false; ///< Is the job still running. This is accessed by multiple threads and reads may be stale.
|
||||||
std::atomic<bool> job_aborted = false; ///< Has the job been aborted. This is accessed by multiple threads and reads may be stale.
|
std::atomic<bool> job_aborted = false; ///< Has the job been aborted. This is accessed by multiple threads and reads may be stale.
|
||||||
|
|
||||||
void EraseFlows(NodeID from);
|
void EraseFlows(StationID from);
|
||||||
void JoinThread();
|
void JoinThread();
|
||||||
void SpawnThread();
|
void SpawnThread();
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ add_files(
|
||||||
getoptdata.cpp
|
getoptdata.cpp
|
||||||
getoptdata.h
|
getoptdata.h
|
||||||
hashtable.hpp
|
hashtable.hpp
|
||||||
|
history.cpp
|
||||||
history_func.hpp
|
history_func.hpp
|
||||||
history_type.hpp
|
history_type.hpp
|
||||||
lrucache.hpp
|
lrucache.hpp
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* This file is part of OpenTTD.
|
||||||
|
* OpenTTD 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, version 2.
|
||||||
|
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @file history.cpp Implementation of functions for storing historical data. */
|
||||||
|
|
||||||
|
#include "../stdafx.h"
|
||||||
|
|
||||||
|
#include "../core/bitmath_func.hpp"
|
||||||
|
#include "history_type.hpp"
|
||||||
|
#include "history_func.hpp"
|
||||||
|
|
||||||
|
#include "../safeguards.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update mask of valid records for a historical data.
|
||||||
|
* @note Call only for the largest history range sub-division.
|
||||||
|
* @param[in,out] valid_history Valid history records.
|
||||||
|
* @param hr History range to update mask for.
|
||||||
|
* @param cur_month Current economy month.
|
||||||
|
*/
|
||||||
|
void UpdateValidHistory(ValidHistoryMask &valid_history, const HistoryRange &hr, uint cur_month)
|
||||||
|
{
|
||||||
|
/* Update for subdivisions first. */
|
||||||
|
if (hr.hr != nullptr) UpdateValidHistory(valid_history, *hr.hr, cur_month);
|
||||||
|
|
||||||
|
/* No need to update if our last entry is marked valid. */
|
||||||
|
if (HasBit(valid_history, hr.last - 1)) return;
|
||||||
|
/* Is it the right time for this history range? */
|
||||||
|
if (cur_month % hr.total_division != 0) return;
|
||||||
|
/* Is the previous history range valid yet? */
|
||||||
|
if (hr.division != 1 && !HasBit(valid_history, hr.first - hr.division)) return;
|
||||||
|
|
||||||
|
SB(valid_history, hr.first, hr.records, GB(valid_history, hr.first, hr.records) << 1ULL | 1ULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if history data is valid, without extracting data.
|
||||||
|
* @param valid_history Mask of valid history records.
|
||||||
|
* @param hr History range to test.
|
||||||
|
* @param age Age of data to test.
|
||||||
|
* @return True iff the data for history range and age is valid.
|
||||||
|
*/
|
||||||
|
bool IsValidHistory(ValidHistoryMask valid_history, const HistoryRange &hr, uint age)
|
||||||
|
{
|
||||||
|
if (hr.hr == nullptr) {
|
||||||
|
if (age < hr.periods) {
|
||||||
|
uint slot = hr.first + age;
|
||||||
|
return HasBit(valid_history, slot);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (age * hr.division < static_cast<uint>(hr.hr->periods - hr.division)) {
|
||||||
|
uint start = age * hr.division + ((TimerGameEconomy::month / hr.hr->division) % hr.division);
|
||||||
|
return IsValidHistory(valid_history, *hr.hr, start);
|
||||||
|
}
|
||||||
|
if (age < hr.periods) {
|
||||||
|
uint slot = hr.first + age - ((hr.hr->periods / hr.division) - 1);
|
||||||
|
return HasBit(valid_history, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -15,25 +15,44 @@
|
||||||
#include "../timer/timer_game_economy.h"
|
#include "../timer/timer_game_economy.h"
|
||||||
#include "history_type.hpp"
|
#include "history_type.hpp"
|
||||||
|
|
||||||
/**
|
void UpdateValidHistory(ValidHistoryMask &valid_history, const HistoryRange &hr, uint cur_month);
|
||||||
* Update mask of valid history records.
|
bool IsValidHistory(ValidHistoryMask valid_history, const HistoryRange &hr, uint age);
|
||||||
* @param[in,out] valid_history Valid history records.
|
|
||||||
*/
|
|
||||||
inline void UpdateValidHistory(ValidHistoryMask &valid_history)
|
|
||||||
{
|
|
||||||
SB(valid_history, LAST_MONTH, HISTORY_RECORDS - LAST_MONTH, GB(valid_history, LAST_MONTH, HISTORY_RECORDS - LAST_MONTH) << 1ULL | 1ULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotate history.
|
* Sum history data elements.
|
||||||
|
* @note The summation should prevent overflowing, and perform transformations relevant to the type of data.
|
||||||
* @tparam T type of history data element.
|
* @tparam T type of history data element.
|
||||||
* @param history Historical data to rotate.
|
* @param history History elements to sum.
|
||||||
|
* @return Sum of history elements.
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void RotateHistory(HistoryData<T> &history)
|
T SumHistory(typename std::span<const T> history);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotate historical data.
|
||||||
|
* @note Call only for the largest history range sub-division.
|
||||||
|
* @tparam T type of history data element.
|
||||||
|
* @param history Historical data to rotate.
|
||||||
|
* @param valid_history Mask of valid history records.
|
||||||
|
* @param hr History range to rotate..
|
||||||
|
* @param cur_month Current economy month.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
void RotateHistory(HistoryData<T> &history, ValidHistoryMask valid_history, const HistoryRange &hr, uint cur_month)
|
||||||
{
|
{
|
||||||
std::rotate(std::rbegin(history), std::rbegin(history) + 1, std::rend(history));
|
if (hr.hr != nullptr) RotateHistory(history, valid_history, *hr.hr, cur_month);
|
||||||
history[THIS_MONTH] = {};
|
if (cur_month % hr.total_division != 0) return;
|
||||||
|
|
||||||
|
std::move_backward(std::next(std::begin(history), hr.first), std::next(std::begin(history), hr.last - 1), std::next(std::begin(history), hr.last));
|
||||||
|
|
||||||
|
if (hr.total_division == 1) {
|
||||||
|
history[hr.first] = history[hr.first - 1];
|
||||||
|
history.front() = {};
|
||||||
|
} else if (HasBit(valid_history, hr.first - hr.division)) {
|
||||||
|
auto first = std::next(std::begin(history), hr.first - hr.division);
|
||||||
|
auto last = std::next(first, hr.division);
|
||||||
|
history[hr.first] = SumHistory<T>(std::span{first, last});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,19 +68,60 @@ T GetAndResetAccumulatedAverage(Taccrued &total)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get historical data.
|
||||||
|
* @tparam T type of history data element.
|
||||||
|
* @param history History data to extract from.
|
||||||
|
* @param valid_history Mask of valid history records.
|
||||||
|
* @param hr History range to get.
|
||||||
|
* @param age Age of data to get.
|
||||||
|
* @param cur_month Current economy month.
|
||||||
|
* @param[out] result Extracted historical data.
|
||||||
|
* @return True iff the data for this history range and age is valid.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
bool GetHistory(const HistoryData<T> &history, ValidHistoryMask valid_history, const HistoryRange &hr, uint age, T &result)
|
||||||
|
{
|
||||||
|
if (hr.hr == nullptr) {
|
||||||
|
if (age < hr.periods) {
|
||||||
|
uint slot = hr.first + age;
|
||||||
|
result = history[slot];
|
||||||
|
return HasBit(valid_history, slot);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (age * hr.division < static_cast<uint>(hr.hr->periods - hr.division)) {
|
||||||
|
bool is_valid = false;
|
||||||
|
std::array<T, HISTORY_MAX_DIVISION> tmp_result; // No need to clear as we fill every element we use.
|
||||||
|
uint start = age * hr.division + ((TimerGameEconomy::month / hr.hr->division) % hr.division);
|
||||||
|
for (auto i = start; i != start + hr.division; ++i) {
|
||||||
|
is_valid |= GetHistory(history, valid_history, *hr.hr, i, tmp_result[i - start]);
|
||||||
|
}
|
||||||
|
result = SumHistory<T>(std::span{std::begin(tmp_result), hr.division});
|
||||||
|
return is_valid;
|
||||||
|
}
|
||||||
|
if (age < hr.periods) {
|
||||||
|
uint slot = hr.first + age - ((hr.hr->periods / hr.division) - 1);
|
||||||
|
result = history[slot];
|
||||||
|
return HasBit(valid_history, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill some data with historical data.
|
* Fill some data with historical data.
|
||||||
* @param history Historical data to fill from.
|
* @param history Historical data to fill from.
|
||||||
* @param valid_history Mask of valid history records.
|
* @param valid_history Mask of valid history records.
|
||||||
|
* @param hr History range to fill with.
|
||||||
* @param fillers Fillers to fill with history data.
|
* @param fillers Fillers to fill with history data.
|
||||||
*/
|
*/
|
||||||
template <uint N, typename T, typename... Tfillers>
|
template <uint N, typename T, typename... Tfillers>
|
||||||
void FillFromHistory(const HistoryData<T> &history, ValidHistoryMask valid_history, Tfillers... fillers)
|
void FillFromHistory(const HistoryData<T> &history, ValidHistoryMask valid_history, const HistoryRange &hr, Tfillers... fillers)
|
||||||
{
|
{
|
||||||
|
T result{};
|
||||||
for (uint i = 0; i != N; ++i) {
|
for (uint i = 0; i != N; ++i) {
|
||||||
if (HasBit(valid_history, N - i)) {
|
if (GetHistory(history, valid_history, hr, N - i - 1, result)) {
|
||||||
auto &data = history[N - i];
|
(fillers.Fill(i, result), ...);
|
||||||
(fillers.Fill(i, data), ...);
|
|
||||||
} else {
|
} else {
|
||||||
(fillers.MakeInvalid(i), ...);
|
(fillers.MakeInvalid(i), ...);
|
||||||
}
|
}
|
||||||
|
@ -71,13 +131,14 @@ void FillFromHistory(const HistoryData<T> &history, ValidHistoryMask valid_histo
|
||||||
/**
|
/**
|
||||||
* Fill some data with empty records.
|
* Fill some data with empty records.
|
||||||
* @param valid_history Mask of valid history records.
|
* @param valid_history Mask of valid history records.
|
||||||
|
* @param hr History range to fill with.
|
||||||
* @param fillers Fillers to fill with history data.
|
* @param fillers Fillers to fill with history data.
|
||||||
*/
|
*/
|
||||||
template <uint N, typename... Tfillers>
|
template <uint N, typename... Tfillers>
|
||||||
void FillFromEmpty(ValidHistoryMask valid_history, Tfillers... fillers)
|
void FillFromEmpty(ValidHistoryMask valid_history, const HistoryRange &hr, Tfillers... fillers)
|
||||||
{
|
{
|
||||||
for (uint i = 0; i != N; ++i) {
|
for (uint i = 0; i != N; ++i) {
|
||||||
if (HasBit(valid_history, N - i)) {
|
if (IsValidHistory(valid_history, hr, N - i - 1)) {
|
||||||
(fillers.MakeZero(i), ...);
|
(fillers.MakeZero(i), ...);
|
||||||
} else {
|
} else {
|
||||||
(fillers.MakeInvalid(i), ...);
|
(fillers.MakeInvalid(i), ...);
|
||||||
|
|
|
@ -10,7 +10,37 @@
|
||||||
#ifndef HISTORY_TYPE_HPP
|
#ifndef HISTORY_TYPE_HPP
|
||||||
#define HISTORY_TYPE_HPP
|
#define HISTORY_TYPE_HPP
|
||||||
|
|
||||||
static constexpr uint8_t HISTORY_RECORDS = 25;
|
struct HistoryRange {
|
||||||
|
const HistoryRange *hr;
|
||||||
|
const uint8_t periods; ///< Number of periods for this range.
|
||||||
|
const uint8_t records; ///< Number of records needed for this range.
|
||||||
|
const uint8_t first; ///< Index of first element in history data.
|
||||||
|
const uint8_t last; ///< Index of last element in history data.
|
||||||
|
const uint8_t division; ///< Number of divisions of the previous history range.
|
||||||
|
const uint8_t total_division; ///< Number of divisions of the initial history range.
|
||||||
|
|
||||||
|
explicit constexpr HistoryRange(uint8_t periods) :
|
||||||
|
hr(nullptr), periods(periods), records(this->periods), first(1), last(this->first + this->records), division(1), total_division(1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr HistoryRange(const HistoryRange &hr, uint8_t division, uint8_t periods) :
|
||||||
|
hr(&hr), periods(periods), records(this->periods - ((hr.periods / division) - 1)), first(hr.last), last(this->first + this->records),
|
||||||
|
division(division), total_division(division * hr.total_division)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr uint8_t HISTORY_PERIODS = 24;
|
||||||
|
static constexpr HistoryRange HISTORY_MONTH{HISTORY_PERIODS};
|
||||||
|
static constexpr HistoryRange HISTORY_QUARTER{HISTORY_MONTH, 3, HISTORY_PERIODS};
|
||||||
|
static constexpr HistoryRange HISTORY_YEAR{HISTORY_QUARTER, 4, HISTORY_PERIODS};
|
||||||
|
|
||||||
|
/** Maximum number of divisions from previous history range. */
|
||||||
|
static constexpr uint8_t HISTORY_MAX_DIVISION = std::max({HISTORY_MONTH.division, HISTORY_QUARTER.division, HISTORY_YEAR.division});
|
||||||
|
|
||||||
|
/** Total number of records require for all history data. */
|
||||||
|
static constexpr uint8_t HISTORY_RECORDS = HISTORY_YEAR.last;
|
||||||
|
|
||||||
static constexpr uint8_t THIS_MONTH = 0;
|
static constexpr uint8_t THIS_MONTH = 0;
|
||||||
static constexpr uint8_t LAST_MONTH = 1;
|
static constexpr uint8_t LAST_MONTH = 1;
|
||||||
|
|
|
@ -368,7 +368,7 @@ StationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, VehicleOrderI
|
||||||
next = v->cur_implicit_order_index;
|
next = v->cur_implicit_order_index;
|
||||||
if (next >= this->GetNumOrders()) {
|
if (next >= this->GetNumOrders()) {
|
||||||
next = this->GetFirstOrder();
|
next = this->GetFirstOrder();
|
||||||
if (next == INVALID_VEH_ORDER_ID) return StationID::Invalid().base();
|
if (next == INVALID_VEH_ORDER_ID) return StationID::Invalid();
|
||||||
} else {
|
} else {
|
||||||
/* GetNext never returns INVALID_VEH_ORDER_ID if there is a valid station in the list.
|
/* GetNext never returns INVALID_VEH_ORDER_ID if there is a valid station in the list.
|
||||||
* As the given "next" is already valid and a station in the list, we
|
* As the given "next" is already valid and a station in the list, we
|
||||||
|
@ -404,11 +404,11 @@ StationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, VehicleOrderI
|
||||||
if (next == INVALID_VEH_ORDER_ID || ((orders[next].IsType(OT_GOTO_STATION) || orders[next].IsType(OT_IMPLICIT)) &&
|
if (next == INVALID_VEH_ORDER_ID || ((orders[next].IsType(OT_GOTO_STATION) || orders[next].IsType(OT_IMPLICIT)) &&
|
||||||
orders[next].GetDestination() == v->last_station_visited &&
|
orders[next].GetDestination() == v->last_station_visited &&
|
||||||
(orders[next].GetUnloadType() & (OUFB_TRANSFER | OUFB_UNLOAD)) != 0)) {
|
(orders[next].GetUnloadType() & (OUFB_TRANSFER | OUFB_UNLOAD)) != 0)) {
|
||||||
return StationID::Invalid().base();
|
return StationID::Invalid();
|
||||||
}
|
}
|
||||||
} while (orders[next].IsType(OT_GOTO_DEPOT) || orders[next].GetDestination() == v->last_station_visited);
|
} while (orders[next].IsType(OT_GOTO_DEPOT) || orders[next].GetDestination() == v->last_station_visited);
|
||||||
|
|
||||||
return orders[next].GetDestination().ToStationID().base();
|
return orders[next].GetDestination().ToStationID();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5079,7 +5079,7 @@ StationIDStack FlowStatMap::DeleteFlows(StationID via)
|
||||||
FlowStat &s_flows = f_it->second;
|
FlowStat &s_flows = f_it->second;
|
||||||
s_flows.ChangeShare(via, INT_MIN);
|
s_flows.ChangeShare(via, INT_MIN);
|
||||||
if (s_flows.GetShares()->empty()) {
|
if (s_flows.GetShares()->empty()) {
|
||||||
ret.Push(f_it->first.base());
|
ret.Push(f_it->first);
|
||||||
this->erase(f_it++);
|
this->erase(f_it++);
|
||||||
} else {
|
} else {
|
||||||
++f_it;
|
++f_it;
|
||||||
|
|
|
@ -26,7 +26,7 @@ struct RoadStop;
|
||||||
struct StationSpec;
|
struct StationSpec;
|
||||||
struct Waypoint;
|
struct Waypoint;
|
||||||
|
|
||||||
using StationIDStack = SmallStack<StationID::BaseType, StationID::BaseType, StationID::Invalid().base(), 8, StationID::End().base()>;
|
using StationIDStack = SmallStack<StationID, StationID::BaseType, StationID::Invalid().base(), 8, StationID::End().base()>;
|
||||||
|
|
||||||
/** Station types */
|
/** Station types */
|
||||||
enum class StationType : uint8_t {
|
enum class StationType : uint8_t {
|
||||||
|
|
|
@ -3,6 +3,7 @@ add_test_files(
|
||||||
bitmath_func.cpp
|
bitmath_func.cpp
|
||||||
enum_over_optimisation.cpp
|
enum_over_optimisation.cpp
|
||||||
flatset_type.cpp
|
flatset_type.cpp
|
||||||
|
history_func.cpp
|
||||||
landscape_partial_pixel_z.cpp
|
landscape_partial_pixel_z.cpp
|
||||||
math_func.cpp
|
math_func.cpp
|
||||||
mock_environment.h
|
mock_environment.h
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* This file is part of OpenTTD.
|
||||||
|
* OpenTTD 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, version 2.
|
||||||
|
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @file history_func.cpp Test functionality for misc/history_func. */
|
||||||
|
|
||||||
|
#include "../stdafx.h"
|
||||||
|
|
||||||
|
#include "../3rdparty/catch2/catch.hpp"
|
||||||
|
|
||||||
|
#include "../misc/history_type.hpp"
|
||||||
|
#include "../misc/history_func.hpp"
|
||||||
|
|
||||||
|
#include "../safeguards.h"
|
||||||
|
|
||||||
|
template <>
|
||||||
|
uint16_t SumHistory(std::span<const uint16_t> history)
|
||||||
|
{
|
||||||
|
uint32_t total = std::accumulate(std::begin(history), std::end(history), 0, [](uint32_t r, const uint16_t &value) { return r + value; });
|
||||||
|
return ClampTo<uint16_t>(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to get history records and return the value, instead returning its validity.
|
||||||
|
* @param history History data to extract from.
|
||||||
|
* @param hr History range to get.
|
||||||
|
* @param age Age of data to get.
|
||||||
|
* @return Historical value for the period and age.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
T GetHistory(const HistoryData<T> &history, const HistoryRange &hr, uint age)
|
||||||
|
{
|
||||||
|
T result;
|
||||||
|
GetHistory(history, 0, hr, age, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("History Rotation and Reporting tests")
|
||||||
|
{
|
||||||
|
HistoryData<uint16_t> history{};
|
||||||
|
ValidHistoryMask valid_history = 0;
|
||||||
|
|
||||||
|
/* Fill the history with decreasing data points for 24 years of history. This ensures that no data period should
|
||||||
|
* contain the same value as another period. */
|
||||||
|
uint16_t i = 12 * HISTORY_PERIODS;
|
||||||
|
for (uint date = 1; date <= 12 * HISTORY_PERIODS; ++date, --i) {
|
||||||
|
history[THIS_MONTH] = i;
|
||||||
|
UpdateValidHistory(valid_history, HISTORY_YEAR, date % 12);
|
||||||
|
RotateHistory(history, valid_history, HISTORY_YEAR, date % 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* With the decreasing sequence, the expected value is triangle number (x*x+n)/2 and the square of the total divisions.
|
||||||
|
* for quarters: 1 + 2 + 3 = 6, 4 + 5 + 6 = 15, 7 + 8 + 9 = 24, 10 + 11 + 12 = 33
|
||||||
|
* 13 + 14 + 15 = 42, 16 + 17 + 18 = 51, 19 + 20 + 21 = 60, 22 + 23 + 24 = 69...
|
||||||
|
* for years: 6 + 15 + 24 + 33 = 78, 42 + 51 + 60 + 69 = 222...
|
||||||
|
*/
|
||||||
|
for (uint j = 0; j < HISTORY_PERIODS; ++j) {
|
||||||
|
CHECK(GetHistory(history, HISTORY_MONTH, j) == (( 1 * 1 + 1) / 2) + 1 * 1 * j);
|
||||||
|
CHECK(GetHistory(history, HISTORY_QUARTER, j) == (( 3 * 3 + 3) / 2) + 3 * 3 * j);
|
||||||
|
CHECK(GetHistory(history, HISTORY_YEAR, j) == ((12 * 12 + 12) / 2) + 12 * 12 * j);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Double-check quarter history matches summed month history. */
|
||||||
|
CHECK(GetHistory(history, HISTORY_MONTH, 0) + GetHistory(history, HISTORY_MONTH, 1) + GetHistory(history, HISTORY_MONTH, 2) == GetHistory(history, HISTORY_QUARTER, 0));
|
||||||
|
CHECK(GetHistory(history, HISTORY_MONTH, 3) + GetHistory(history, HISTORY_MONTH, 4) + GetHistory(history, HISTORY_MONTH, 5) == GetHistory(history, HISTORY_QUARTER, 1));
|
||||||
|
CHECK(GetHistory(history, HISTORY_MONTH, 6) + GetHistory(history, HISTORY_MONTH, 7) + GetHistory(history, HISTORY_MONTH, 8) == GetHistory(history, HISTORY_QUARTER, 2));
|
||||||
|
CHECK(GetHistory(history, HISTORY_MONTH, 9) + GetHistory(history, HISTORY_MONTH, 10) + GetHistory(history, HISTORY_MONTH, 11) == GetHistory(history, HISTORY_QUARTER, 3));
|
||||||
|
CHECK(GetHistory(history, HISTORY_MONTH, 12) + GetHistory(history, HISTORY_MONTH, 13) + GetHistory(history, HISTORY_MONTH, 14) == GetHistory(history, HISTORY_QUARTER, 4));
|
||||||
|
CHECK(GetHistory(history, HISTORY_MONTH, 15) + GetHistory(history, HISTORY_MONTH, 16) + GetHistory(history, HISTORY_MONTH, 17) == GetHistory(history, HISTORY_QUARTER, 5));
|
||||||
|
CHECK(GetHistory(history, HISTORY_MONTH, 18) + GetHistory(history, HISTORY_MONTH, 19) + GetHistory(history, HISTORY_MONTH, 20) == GetHistory(history, HISTORY_QUARTER, 6));
|
||||||
|
CHECK(GetHistory(history, HISTORY_MONTH, 21) + GetHistory(history, HISTORY_MONTH, 22) + GetHistory(history, HISTORY_MONTH, 23) == GetHistory(history, HISTORY_QUARTER, 7));
|
||||||
|
|
||||||
|
/* Double-check year history matches summed quarter history. */
|
||||||
|
CHECK(GetHistory(history, HISTORY_QUARTER, 0) + GetHistory(history, HISTORY_QUARTER, 1) + GetHistory(history, HISTORY_QUARTER, 2) + GetHistory(history, HISTORY_QUARTER, 3) == GetHistory(history, HISTORY_YEAR, 0));
|
||||||
|
CHECK(GetHistory(history, HISTORY_QUARTER, 4) + GetHistory(history, HISTORY_QUARTER, 5) + GetHistory(history, HISTORY_QUARTER, 6) + GetHistory(history, HISTORY_QUARTER, 7) == GetHistory(history, HISTORY_YEAR, 1));
|
||||||
|
CHECK(GetHistory(history, HISTORY_QUARTER, 8) + GetHistory(history, HISTORY_QUARTER, 9) + GetHistory(history, HISTORY_QUARTER, 10) + GetHistory(history, HISTORY_QUARTER, 11) == GetHistory(history, HISTORY_YEAR, 2));
|
||||||
|
CHECK(GetHistory(history, HISTORY_QUARTER, 12) + GetHistory(history, HISTORY_QUARTER, 13) + GetHistory(history, HISTORY_QUARTER, 14) + GetHistory(history, HISTORY_QUARTER, 15) == GetHistory(history, HISTORY_YEAR, 3));
|
||||||
|
CHECK(GetHistory(history, HISTORY_QUARTER, 16) + GetHistory(history, HISTORY_QUARTER, 17) + GetHistory(history, HISTORY_QUARTER, 18) + GetHistory(history, HISTORY_QUARTER, 19) == GetHistory(history, HISTORY_YEAR, 4));
|
||||||
|
CHECK(GetHistory(history, HISTORY_QUARTER, 20) + GetHistory(history, HISTORY_QUARTER, 21) + GetHistory(history, HISTORY_QUARTER, 22) + GetHistory(history, HISTORY_QUARTER, 23) == GetHistory(history, HISTORY_YEAR, 5));
|
||||||
|
}
|
|
@ -741,7 +741,7 @@ public:
|
||||||
*/
|
*/
|
||||||
inline StationIDStack GetNextStoppingStation() const
|
inline StationIDStack GetNextStoppingStation() const
|
||||||
{
|
{
|
||||||
return (this->orders == nullptr) ? StationID::Invalid().base() : this->orders->GetNextStoppingStation(this);
|
return (this->orders == nullptr) ? StationID::Invalid() : this->orders->GetNextStoppingStation(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResetRefitCaps();
|
void ResetRefitCaps();
|
||||||
|
|
|
@ -37,6 +37,7 @@ enum GraphWidgets : WidgetID {
|
||||||
WID_GRAPH_MATRIX_SCROLLBAR,///< Cargo list scrollbar.
|
WID_GRAPH_MATRIX_SCROLLBAR,///< Cargo list scrollbar.
|
||||||
|
|
||||||
WID_GRAPH_RANGE_MATRIX, ///< Range list.
|
WID_GRAPH_RANGE_MATRIX, ///< Range list.
|
||||||
|
WID_GRAPH_SCALE_MATRIX, ///< Horizontal axis scale list.
|
||||||
|
|
||||||
WID_PHG_DETAILED_PERFORMANCE, ///< Detailed performance.
|
WID_PHG_DETAILED_PERFORMANCE, ///< Detailed performance.
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue