OpenTTD
framerate_gui.cpp
Go to the documentation of this file.
1 /* $Id$ */
2 
3 /*
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
8 */
9 
12 #include "framerate_type.h"
13 #include <chrono>
14 #include "gfx_func.h"
15 #include "window_gui.h"
16 #include "window_func.h"
17 #include "table/sprites.h"
18 #include "string_func.h"
19 #include "strings_func.h"
20 #include "console_func.h"
21 #include "console_type.h"
22 #include "guitimer_func.h"
23 #include "company_base.h"
24 #include "ai/ai_info.hpp"
25 
27 #include "safeguards.h"
28 
29 
33 namespace {
34 
36  const int NUM_FRAMERATE_POINTS = 512;
39 
40  struct PerformanceData {
42  static const TimingMeasurement INVALID_DURATION = UINT64_MAX;
43 
49  double expected_rate;
55  int num_valid;
56 
61 
68  explicit PerformanceData(double expected_rate) : expected_rate(expected_rate), next_index(0), prev_index(0), num_valid(0) { }
69 
71  void Add(TimingMeasurement start_time, TimingMeasurement end_time)
72  {
73  this->durations[this->next_index] = end_time - start_time;
74  this->timestamps[this->next_index] = start_time;
75  this->prev_index = this->next_index;
76  this->next_index += 1;
77  if (this->next_index >= NUM_FRAMERATE_POINTS) this->next_index = 0;
78  this->num_valid = min(NUM_FRAMERATE_POINTS, this->num_valid + 1);
79  }
80 
83  {
84  this->timestamps[this->next_index] = this->acc_timestamp;
85  this->durations[this->next_index] = this->acc_duration;
86  this->prev_index = this->next_index;
87  this->next_index += 1;
88  if (this->next_index >= NUM_FRAMERATE_POINTS) this->next_index = 0;
89  this->num_valid = min(NUM_FRAMERATE_POINTS, this->num_valid + 1);
90 
91  this->acc_duration = 0;
92  this->acc_timestamp = start_time;
93  }
94 
97  {
98  this->acc_duration += duration;
99  }
100 
102  void AddPause(TimingMeasurement start_time)
103  {
104  if (this->durations[this->prev_index] != INVALID_DURATION) {
105  this->timestamps[this->next_index] = start_time;
106  this->durations[this->next_index] = INVALID_DURATION;
107  this->prev_index = this->next_index;
108  this->next_index += 1;
109  if (this->next_index >= NUM_FRAMERATE_POINTS) this->next_index = 0;
110  this->num_valid += 1;
111  }
112  }
113 
116  {
117  count = min(count, this->num_valid);
118 
119  int first_point = this->prev_index - count;
120  if (first_point < 0) first_point += NUM_FRAMERATE_POINTS;
121 
122  /* Sum durations, skipping invalid points */
123  double sumtime = 0;
124  for (int i = first_point; i < first_point + count; i++) {
125  auto d = this->durations[i % NUM_FRAMERATE_POINTS];
126  if (d != INVALID_DURATION) {
127  sumtime += d;
128  } else {
129  /* Don't count the invalid durations */
130  count--;
131  }
132  }
133 
134  if (count == 0) return 0; // avoid div by zero
135  return sumtime * 1000 / count / TIMESTAMP_PRECISION;
136  }
137 
139  double GetRate()
140  {
141  /* Start at last recorded point, end at latest when reaching the earliest recorded point */
142  int point = this->prev_index;
143  int last_point = this->next_index - this->num_valid;
144  if (last_point < 0) last_point += NUM_FRAMERATE_POINTS;
145 
146  /* Number of data points collected */
147  int count = 0;
148  /* Time of previous data point */
149  TimingMeasurement last = this->timestamps[point];
150  /* Total duration covered by collected points */
151  TimingMeasurement total = 0;
152 
153  while (point != last_point) {
154  /* Only record valid data points, but pretend the gaps in measurements aren't there */
155  if (this->durations[point] != INVALID_DURATION) {
156  total += last - this->timestamps[point];
157  count++;
158  }
159  last = this->timestamps[point];
160  if (total >= TIMESTAMP_PRECISION) break; // end after 1 second has been collected
161  point--;
162  if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
163  }
164 
165  if (total == 0 || count == 0) return 0;
166  return (double)count * TIMESTAMP_PRECISION / total;
167  }
168  };
169 
171  static const double GL_RATE = 1000.0 / MILLISECONDS_PER_TICK;
172 
179  PerformanceData(GL_RATE), // PFE_GAMELOOP
180  PerformanceData(1), // PFE_ACC_GL_ECONOMY
181  PerformanceData(1), // PFE_ACC_GL_TRAINS
182  PerformanceData(1), // PFE_ACC_GL_ROADVEHS
183  PerformanceData(1), // PFE_ACC_GL_SHIPS
184  PerformanceData(1), // PFE_ACC_GL_AIRCRAFT
185  PerformanceData(1), // PFE_GL_LANDSCAPE
186  PerformanceData(1), // PFE_GL_LINKGRAPH
187  PerformanceData(GL_RATE), // PFE_DRAWING
188  PerformanceData(1), // PFE_ACC_DRAWWORLD
189  PerformanceData(60.0), // PFE_VIDEO
190  PerformanceData(1000.0 * 8192 / 44100), // PFE_SOUND
191  PerformanceData(1), // PFE_ALLSCRIPTS
192  PerformanceData(1), // PFE_GAMESCRIPT
193  PerformanceData(1), // PFE_AI0 ...
194  PerformanceData(1),
195  PerformanceData(1),
196  PerformanceData(1),
197  PerformanceData(1),
198  PerformanceData(1),
199  PerformanceData(1),
200  PerformanceData(1),
201  PerformanceData(1),
202  PerformanceData(1),
203  PerformanceData(1),
204  PerformanceData(1),
205  PerformanceData(1),
206  PerformanceData(1),
207  PerformanceData(1), // PFE_AI14
208  };
209 
210 }
211 
212 
219 {
220  using namespace std::chrono;
221  return (TimingMeasurement)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count();
222 }
223 
224 
230 {
231  assert(elem < PFE_MAX);
232 
233  this->elem = elem;
234  this->start_time = GetPerformanceTimer();
235 }
236 
239 {
240  if (this->elem == PFE_ALLSCRIPTS) {
241  /* Hack to not record scripts total when no scripts are active */
242  bool any_active = _pf_data[PFE_GAMESCRIPT].num_valid > 0;
243  for (uint e = PFE_AI0; e < PFE_MAX; e++) any_active |= _pf_data[e].num_valid > 0;
244  if (!any_active) {
246  return;
247  }
248  }
249  _pf_data[this->elem].Add(this->start_time, GetPerformanceTimer());
250 }
251 
254 {
255  _pf_data[this->elem].expected_rate = rate;
256 }
257 
260 {
261  _pf_data[elem].num_valid = 0;
262  _pf_data[elem].next_index = 0;
263  _pf_data[elem].prev_index = 0;
264 }
265 
271 {
273 }
274 
275 
281 {
282  assert(elem < PFE_MAX);
283 
284  this->elem = elem;
285  this->start_time = GetPerformanceTimer();
286 }
287 
290 {
291  _pf_data[this->elem].AddAccumulate(GetPerformanceTimer() - this->start_time);
292 }
293 
300 {
302 }
303 
304 
306 
307 
308 static const PerformanceElement DISPLAY_ORDER_PFE[PFE_MAX] = {
309  PFE_GAMELOOP,
313  PFE_GL_SHIPS,
318  PFE_AI0,
319  PFE_AI1,
320  PFE_AI2,
321  PFE_AI3,
322  PFE_AI4,
323  PFE_AI5,
324  PFE_AI6,
325  PFE_AI7,
326  PFE_AI8,
327  PFE_AI9,
328  PFE_AI10,
329  PFE_AI11,
330  PFE_AI12,
331  PFE_AI13,
332  PFE_AI14,
334  PFE_DRAWING,
336  PFE_VIDEO,
337  PFE_SOUND,
338 };
339 
340 static const char * GetAIName(int ai_index)
341 {
342  if (!Company::IsValidAiID(ai_index)) return "";
343  return Company::Get(ai_index)->ai_info->GetName();
344 }
345 
347 static const NWidgetPart _framerate_window_widgets[] = {
349  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
350  NWidget(WWT_CAPTION, COLOUR_GREY, WID_FRW_CAPTION), SetDataTip(STR_FRAMERATE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
351  NWidget(WWT_SHADEBOX, COLOUR_GREY),
352  NWidget(WWT_STICKYBOX, COLOUR_GREY),
353  EndContainer(),
354  NWidget(WWT_PANEL, COLOUR_GREY),
355  NWidget(NWID_VERTICAL), SetPadding(6), SetPIP(0, 3, 0),
356  NWidget(WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_GAMELOOP), SetDataTip(STR_FRAMERATE_RATE_GAMELOOP, STR_FRAMERATE_RATE_GAMELOOP_TOOLTIP),
357  NWidget(WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_DRAWING), SetDataTip(STR_FRAMERATE_RATE_BLITTER, STR_FRAMERATE_RATE_BLITTER_TOOLTIP),
358  NWidget(WWT_TEXT, COLOUR_GREY, WID_FRW_RATE_FACTOR), SetDataTip(STR_FRAMERATE_SPEED_FACTOR, STR_FRAMERATE_SPEED_FACTOR_TOOLTIP),
359  EndContainer(),
360  EndContainer(),
362  NWidget(WWT_PANEL, COLOUR_GREY),
363  NWidget(NWID_VERTICAL), SetPadding(6), SetPIP(0, 3, 0),
364  NWidget(NWID_HORIZONTAL), SetPIP(0, 6, 0),
365  NWidget(WWT_EMPTY, COLOUR_GREY, WID_FRW_TIMES_NAMES), SetScrollbar(WID_FRW_SCROLLBAR),
366  NWidget(WWT_EMPTY, COLOUR_GREY, WID_FRW_TIMES_CURRENT), SetScrollbar(WID_FRW_SCROLLBAR),
367  NWidget(WWT_EMPTY, COLOUR_GREY, WID_FRW_TIMES_AVERAGE), SetScrollbar(WID_FRW_SCROLLBAR),
368  EndContainer(),
369  NWidget(WWT_TEXT, COLOUR_GREY, WID_FRW_INFO_DATA_POINTS), SetDataTip(STR_FRAMERATE_DATA_POINTS, 0x0),
370  EndContainer(),
371  EndContainer(),
373  NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_FRW_SCROLLBAR),
374  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
375  EndContainer(),
376  EndContainer(),
377 };
378 
380  bool small;
381  GUITimer next_update;
382  int num_active;
383  int num_displayed;
384 
385  struct CachedDecimal {
386  StringID strid;
387  uint32 value;
388 
389  inline void SetRate(double value, double target)
390  {
391  const double threshold_good = target * 0.95;
392  const double threshold_bad = target * 2 / 3;
393  value = min(9999.99, value);
394  this->value = (uint32)(value * 100);
395  this->strid = (value > threshold_good) ? STR_FRAMERATE_FPS_GOOD : (value < threshold_bad) ? STR_FRAMERATE_FPS_BAD : STR_FRAMERATE_FPS_WARN;
396  }
397 
398  inline void SetTime(double value, double target)
399  {
400  const double threshold_good = target / 3;
401  const double threshold_bad = target;
402  value = min(9999.99, value);
403  this->value = (uint32)(value * 100);
404  this->strid = (value < threshold_good) ? STR_FRAMERATE_MS_GOOD : (value > threshold_bad) ? STR_FRAMERATE_MS_BAD : STR_FRAMERATE_MS_WARN;
405  }
406 
407  inline void InsertDParams(uint n) const
408  {
409  SetDParam(n, this->value);
410  SetDParam(n + 1, 2);
411  }
412  };
413 
417  CachedDecimal times_shortterm[PFE_MAX];
418  CachedDecimal times_longterm[PFE_MAX];
419 
420  static const int VSPACING = 3;
421  static const int MIN_ELEMENTS = 5;
422 
423  FramerateWindow(WindowDesc *desc, WindowNumber number) : Window(desc)
424  {
425  this->InitNested(number);
426  this->small = this->IsShaded();
427  this->UpdateData();
428  this->num_displayed = this->num_active;
429  this->next_update.SetInterval(100);
430 
431  /* Window is always initialised to MIN_ELEMENTS height, resize to contain num_displayed */
432  ResizeWindow(this, 0, (max(MIN_ELEMENTS, this->num_displayed) - MIN_ELEMENTS) * FONT_HEIGHT_NORMAL);
433  }
434 
435  virtual void OnRealtimeTick(uint delta_ms)
436  {
437  bool elapsed = this->next_update.Elapsed(delta_ms);
438 
439  /* Check if the shaded state has changed, switch caption text if it has */
440  if (this->small != this->IsShaded()) {
441  this->small = this->IsShaded();
442  this->GetWidget<NWidgetLeaf>(WID_FRW_CAPTION)->SetDataTip(this->small ? STR_FRAMERATE_CAPTION_SMALL : STR_FRAMERATE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
443  elapsed = true;
444  }
445 
446  if (elapsed) {
447  this->UpdateData();
448  this->SetDirty();
449  this->next_update.SetInterval(100);
450  }
451  }
452 
453  void UpdateData()
454  {
455  double gl_rate = _pf_data[PFE_GAMELOOP].GetRate();
456  this->rate_gameloop.SetRate(gl_rate, _pf_data[PFE_GAMELOOP].expected_rate);
457  this->speed_gameloop.SetRate(gl_rate / _pf_data[PFE_GAMELOOP].expected_rate, 1.0);
458  if (this->small) return; // in small mode, this is everything needed
459 
460  this->rate_drawing.SetRate(_pf_data[PFE_DRAWING].GetRate(), _pf_data[PFE_DRAWING].expected_rate);
461 
462  int new_active = 0;
463  for (PerformanceElement e = PFE_FIRST; e < PFE_MAX; e++) {
464  this->times_shortterm[e].SetTime(_pf_data[e].GetAverageDurationMilliseconds(8), MILLISECONDS_PER_TICK);
465  this->times_longterm[e].SetTime(_pf_data[e].GetAverageDurationMilliseconds(NUM_FRAMERATE_POINTS), MILLISECONDS_PER_TICK);
466  if (_pf_data[e].num_valid > 0) new_active++;
467  }
468 
469  if (new_active != this->num_active) {
470  this->num_active = new_active;
471  Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
472  sb->SetCount(this->num_active);
473  sb->SetCapacity(min(this->num_displayed, this->num_active));
474  this->ReInit();
475  }
476  }
477 
478  virtual void SetStringParameters(int widget) const
479  {
480  switch (widget) {
481  case WID_FRW_CAPTION:
482  /* When the window is shaded, the caption shows game loop rate and speed factor */
483  if (!this->small) break;
484  SetDParam(0, this->rate_gameloop.strid);
485  this->rate_gameloop.InsertDParams(1);
486  this->speed_gameloop.InsertDParams(3);
487  break;
488 
489  case WID_FRW_RATE_GAMELOOP:
490  SetDParam(0, this->rate_gameloop.strid);
491  this->rate_gameloop.InsertDParams(1);
492  break;
493  case WID_FRW_RATE_DRAWING:
494  SetDParam(0, this->rate_drawing.strid);
495  this->rate_drawing.InsertDParams(1);
496  break;
497  case WID_FRW_RATE_FACTOR:
498  this->speed_gameloop.InsertDParams(0);
499  break;
500  case WID_FRW_INFO_DATA_POINTS:
502  break;
503  }
504  }
505 
506  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
507  {
508  switch (widget) {
509  case WID_FRW_RATE_GAMELOOP:
510  SetDParam(0, STR_FRAMERATE_FPS_GOOD);
511  SetDParam(1, 999999);
512  SetDParam(2, 2);
513  *size = GetStringBoundingBox(STR_FRAMERATE_RATE_GAMELOOP);
514  break;
515  case WID_FRW_RATE_DRAWING:
516  SetDParam(0, STR_FRAMERATE_FPS_GOOD);
517  SetDParam(1, 999999);
518  SetDParam(2, 2);
519  *size = GetStringBoundingBox(STR_FRAMERATE_RATE_BLITTER);
520  break;
521  case WID_FRW_RATE_FACTOR:
522  SetDParam(0, 999999);
523  SetDParam(1, 2);
524  *size = GetStringBoundingBox(STR_FRAMERATE_SPEED_FACTOR);
525  break;
526 
527  case WID_FRW_TIMES_NAMES: {
528  size->width = 0;
529  size->height = FONT_HEIGHT_NORMAL + VSPACING + MIN_ELEMENTS * FONT_HEIGHT_NORMAL;
530  resize->width = 0;
531  resize->height = FONT_HEIGHT_NORMAL;
532  for (PerformanceElement e : DISPLAY_ORDER_PFE) {
533  if (_pf_data[e].num_valid == 0) continue;
534  Dimension line_size;
535  if (e < PFE_AI0) {
536  line_size = GetStringBoundingBox(STR_FRAMERATE_GAMELOOP + e);
537  } else {
538  SetDParam(0, e - PFE_AI0 + 1);
539  SetDParamStr(1, GetAIName(e - PFE_AI0));
540  line_size = GetStringBoundingBox(STR_FRAMERATE_AI);
541  }
542  size->width = max(size->width, line_size.width);
543  }
544  break;
545  }
546 
547  case WID_FRW_TIMES_CURRENT:
548  case WID_FRW_TIMES_AVERAGE: {
549  *size = GetStringBoundingBox(STR_FRAMERATE_CURRENT + (widget - WID_FRW_TIMES_CURRENT));
550  SetDParam(0, 999999);
551  SetDParam(1, 2);
552  Dimension item_size = GetStringBoundingBox(STR_FRAMERATE_MS_GOOD);
553  size->width = max(size->width, item_size.width);
554  size->height += FONT_HEIGHT_NORMAL * MIN_ELEMENTS + VSPACING;
555  resize->width = 0;
556  resize->height = FONT_HEIGHT_NORMAL;
557  break;
558  }
559  }
560  }
561 
563  void DrawElementTimesColumn(const Rect &r, StringID heading_str, const CachedDecimal *values) const
564  {
565  const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
566  uint16 skip = sb->GetPosition();
567  int drawable = this->num_displayed;
568  int y = r.top;
569  DrawString(r.left, r.right, y, heading_str, TC_FROMSTRING, SA_CENTER, true);
570  y += FONT_HEIGHT_NORMAL + VSPACING;
571  for (PerformanceElement e : DISPLAY_ORDER_PFE) {
572  if (_pf_data[e].num_valid == 0) continue;
573  if (skip > 0) {
574  skip--;
575  } else {
576  values[e].InsertDParams(0);
577  DrawString(r.left, r.right, y, values[e].strid, TC_FROMSTRING, SA_RIGHT);
578  y += FONT_HEIGHT_NORMAL;
579  drawable--;
580  if (drawable == 0) break;
581  }
582  }
583  }
584 
585  virtual void DrawWidget(const Rect &r, int widget) const
586  {
587  switch (widget) {
588  case WID_FRW_TIMES_NAMES: {
589  /* Render a column of titles for performance element names */
590  const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
591  uint16 skip = sb->GetPosition();
592  int drawable = this->num_displayed;
593  int y = r.top + FONT_HEIGHT_NORMAL + VSPACING; // first line contains headings in the value columns
594  for (PerformanceElement e : DISPLAY_ORDER_PFE) {
595  if (_pf_data[e].num_valid == 0) continue;
596  if (skip > 0) {
597  skip--;
598  } else {
599  if (e < PFE_AI0) {
600  DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + e, TC_FROMSTRING, SA_LEFT);
601  } else {
602  SetDParam(0, e - PFE_AI0 + 1);
603  SetDParamStr(1, GetAIName(e - PFE_AI0));
604  DrawString(r.left, r.right, y, STR_FRAMERATE_AI, TC_FROMSTRING, SA_LEFT);
605  }
606  y += FONT_HEIGHT_NORMAL;
607  drawable--;
608  if (drawable == 0) break;
609  }
610  }
611  break;
612  }
613  case WID_FRW_TIMES_CURRENT:
614  /* Render short-term average values */
615  DrawElementTimesColumn(r, STR_FRAMERATE_CURRENT, this->times_shortterm);
616  break;
617  case WID_FRW_TIMES_AVERAGE:
618  /* Render averages of all recorded values */
619  DrawElementTimesColumn(r, STR_FRAMERATE_AVERAGE, this->times_longterm);
620  break;
621  }
622  }
623 
624  virtual void OnClick(Point pt, int widget, int click_count)
625  {
626  switch (widget) {
627  case WID_FRW_TIMES_NAMES:
628  case WID_FRW_TIMES_CURRENT:
629  case WID_FRW_TIMES_AVERAGE: {
630  /* Open time graph windows when clicking detail measurement lines */
631  const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
632  int line = sb->GetScrolledRowFromWidget(pt.y - FONT_HEIGHT_NORMAL - VSPACING, this, widget, VSPACING, FONT_HEIGHT_NORMAL);
633  if (line != INT_MAX) {
634  line++;
635  /* Find the visible line that was clicked */
636  for (PerformanceElement e : DISPLAY_ORDER_PFE) {
637  if (_pf_data[e].num_valid > 0) line--;
638  if (line == 0) {
640  break;
641  }
642  }
643  }
644  break;
645  }
646  }
647  }
648 
649  virtual void OnResize()
650  {
651  auto *wid = this->GetWidget<NWidgetResizeBase>(WID_FRW_TIMES_NAMES);
652  this->num_displayed = (wid->current_y - wid->min_y - VSPACING) / FONT_HEIGHT_NORMAL - 1; // subtract 1 for headings
653  this->GetScrollbar(WID_FRW_SCROLLBAR)->SetCapacity(this->num_displayed);
654  }
655 };
656 
657 static WindowDesc _framerate_display_desc(
658  WDP_AUTO, "framerate_display", 0, 0,
660  0,
661  _framerate_window_widgets, lengthof(_framerate_window_widgets)
662 );
663 
664 
666 static const NWidgetPart _frametime_graph_window_widgets[] = {
668  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
669  NWidget(WWT_CAPTION, COLOUR_GREY, WID_FGW_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
670  NWidget(WWT_STICKYBOX, COLOUR_GREY),
671  EndContainer(),
672  NWidget(WWT_PANEL, COLOUR_GREY),
674  NWidget(WWT_EMPTY, COLOUR_GREY, WID_FGW_GRAPH),
675  EndContainer(),
676  EndContainer(),
677 };
678 
683 
686 
687  FrametimeGraphWindow(WindowDesc *desc, WindowNumber number) : Window(desc)
688  {
689  this->element = (PerformanceElement)number;
690  this->horizontal_scale = 4;
691  this->vertical_scale = TIMESTAMP_PRECISION / 10;
692  this->next_scale_update.SetInterval(1);
693 
694  this->InitNested(number);
695  }
696 
697  virtual void SetStringParameters(int widget) const
698  {
699  switch (widget) {
700  case WID_FGW_CAPTION:
701  if (this->element < PFE_AI0) {
702  SetDParam(0, STR_FRAMETIME_CAPTION_GAMELOOP + this->element);
703  } else {
704  SetDParam(0, STR_FRAMETIME_CAPTION_AI);
705  SetDParam(1, this->element - PFE_AI0 + 1);
706  SetDParamStr(2, GetAIName(this->element - PFE_AI0));
707  }
708  break;
709  }
710  }
711 
712  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
713  {
714  if (widget == WID_FGW_GRAPH) {
715  SetDParam(0, 100);
716  Dimension size_ms_label = GetStringBoundingBox(STR_FRAMERATE_GRAPH_MILLISECONDS);
717  SetDParam(0, 100);
718  Dimension size_s_label = GetStringBoundingBox(STR_FRAMERATE_GRAPH_SECONDS);
719 
720  /* Size graph in height to fit at least 10 vertical labels with space between, or at least 100 pixels */
721  graph_size.height = max<uint>(100, 10 * (size_ms_label.height + 1));
722  /* Always 2:1 graph area */
723  graph_size.width = 2 * graph_size.height;
724  *size = graph_size;
725 
726  size->width += size_ms_label.width + 2;
727  size->height += size_s_label.height + 2;
728  }
729  }
730 
731  void SelectHorizontalScale(TimingMeasurement range)
732  {
733  /* Determine horizontal scale based on period covered by 60 points
734  * (slightly less than 2 seconds at full game speed) */
735  struct ScaleDef { TimingMeasurement range; int scale; };
736  static const ScaleDef hscales[] = {
737  { 120, 60 },
738  { 10, 20 },
739  { 5, 10 },
740  { 3, 4 },
741  { 1, 2 },
742  };
743  for (const ScaleDef *sc = hscales; sc < hscales + lengthof(hscales); sc++) {
744  if (range < sc->range) this->horizontal_scale = sc->scale;
745  }
746  }
747 
748  void SelectVerticalScale(TimingMeasurement range)
749  {
750  /* Determine vertical scale based on peak value (within the horizontal scale + a bit) */
751  static const TimingMeasurement vscales[] = {
752  TIMESTAMP_PRECISION * 100,
753  TIMESTAMP_PRECISION * 10,
754  TIMESTAMP_PRECISION * 5,
756  TIMESTAMP_PRECISION / 2,
757  TIMESTAMP_PRECISION / 5,
758  TIMESTAMP_PRECISION / 10,
759  TIMESTAMP_PRECISION / 50,
760  TIMESTAMP_PRECISION / 200,
761  };
762  for (const TimingMeasurement *sc = vscales; sc < vscales + lengthof(vscales); sc++) {
763  if (range < *sc) this->vertical_scale = (int)*sc;
764  }
765  }
766 
768  void UpdateScale()
769  {
770  const TimingMeasurement *durations = _pf_data[this->element].durations;
771  const TimingMeasurement *timestamps = _pf_data[this->element].timestamps;
772  int num_valid = _pf_data[this->element].num_valid;
773  int point = _pf_data[this->element].prev_index;
774 
775  TimingMeasurement lastts = timestamps[point];
776  TimingMeasurement time_sum = 0;
777  TimingMeasurement peak_value = 0;
778  int count = 0;
779 
780  /* Sensible default for when too few measurements are available */
781  this->horizontal_scale = 4;
782 
783  for (int i = 1; i < num_valid; i++) {
784  point--;
785  if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
786 
787  TimingMeasurement value = durations[point];
788  if (value == PerformanceData::INVALID_DURATION) {
789  /* Skip gaps in data by pretending time is continuous across them */
790  lastts = timestamps[point];
791  continue;
792  }
793  if (value > peak_value) peak_value = value;
794  count++;
795 
796  /* Accumulate period of time covered by data */
797  time_sum += lastts - timestamps[point];
798  lastts = timestamps[point];
799 
800  /* Enough data to select a range and get decent data density */
801  if (count == 60) this->SelectHorizontalScale(time_sum / TIMESTAMP_PRECISION);
802 
803  /* End when enough points have been collected and the horizontal scale has been exceeded */
804  if (count >= 60 && time_sum >= (this->horizontal_scale + 2) * TIMESTAMP_PRECISION / 2) break;
805  }
806 
807  this->SelectVerticalScale(peak_value);
808  }
809 
810  virtual void OnRealtimeTick(uint delta_ms)
811  {
812  this->SetDirty();
813 
814  if (this->next_scale_update.Elapsed(delta_ms)) {
815  this->next_scale_update.SetInterval(500);
816  this->UpdateScale();
817  }
818  }
819 
821  template<typename T>
822  static inline T Scinterlate(T dst_min, T dst_max, T src_min, T src_max, T value)
823  {
824  T dst_diff = dst_max - dst_min;
825  T src_diff = src_max - src_min;
826  return (value - src_min) * dst_diff / src_diff + dst_min;
827  }
828 
829  virtual void DrawWidget(const Rect &r, int widget) const
830  {
831  if (widget == WID_FGW_GRAPH) {
832  const TimingMeasurement *durations = _pf_data[this->element].durations;
833  const TimingMeasurement *timestamps = _pf_data[this->element].timestamps;
834  int point = _pf_data[this->element].prev_index;
835 
836  const int x_zero = r.right - (int)this->graph_size.width;
837  const int x_max = r.right;
838  const int y_zero = r.top + (int)this->graph_size.height;
839  const int y_max = r.top;
840  const int c_grid = PC_DARK_GREY;
841  const int c_lines = PC_BLACK;
842  const int c_peak = PC_DARK_RED;
843 
844  const TimingMeasurement draw_horz_scale = (TimingMeasurement)this->horizontal_scale * TIMESTAMP_PRECISION / 2;
845  const TimingMeasurement draw_vert_scale = (TimingMeasurement)this->vertical_scale;
846 
847  /* Number of \c horizontal_scale units in each horizontal division */
848  const uint horz_div_scl = (this->horizontal_scale <= 20) ? 1 : 10;
849  /* Number of divisions of the horizontal axis */
850  const uint horz_divisions = this->horizontal_scale / horz_div_scl;
851  /* Number of divisions of the vertical axis */
852  const uint vert_divisions = 10;
853 
854  /* Draw division lines and labels for the vertical axis */
855  for (uint division = 0; division < vert_divisions; division++) {
856  int y = Scinterlate(y_zero, y_max, 0, (int)vert_divisions, (int)division);
857  GfxDrawLine(x_zero, y, x_max, y, c_grid);
858  if (division % 2 == 0) {
859  if ((TimingMeasurement)this->vertical_scale > TIMESTAMP_PRECISION) {
860  SetDParam(0, this->vertical_scale * division / 10 / TIMESTAMP_PRECISION);
861  DrawString(r.left, x_zero - 2, y - FONT_HEIGHT_SMALL, STR_FRAMERATE_GRAPH_SECONDS, TC_GREY, SA_RIGHT | SA_FORCE, false, FS_SMALL);
862  } else {
863  SetDParam(0, this->vertical_scale * division / 10 * 1000 / TIMESTAMP_PRECISION);
864  DrawString(r.left, x_zero - 2, y - FONT_HEIGHT_SMALL, STR_FRAMERATE_GRAPH_MILLISECONDS, TC_GREY, SA_RIGHT | SA_FORCE, false, FS_SMALL);
865  }
866  }
867  }
868  /* Draw divison lines and labels for the horizontal axis */
869  for (uint division = horz_divisions; division > 0; division--) {
870  int x = Scinterlate(x_zero, x_max, 0, (int)horz_divisions, (int)horz_divisions - (int)division);
871  GfxDrawLine(x, y_max, x, y_zero, c_grid);
872  if (division % 2 == 0) {
873  SetDParam(0, division * horz_div_scl / 2);
874  DrawString(x, x_max, y_zero + 2, STR_FRAMERATE_GRAPH_SECONDS, TC_GREY, SA_LEFT | SA_FORCE, false, FS_SMALL);
875  }
876  }
877 
878  /* Position of last rendered data point */
879  Point lastpoint = {
880  x_max,
881  (int)Scinterlate<int64>(y_zero, y_max, 0, this->vertical_scale, durations[point])
882  };
883  /* Timestamp of last rendered data point */
884  TimingMeasurement lastts = timestamps[point];
885 
886  TimingMeasurement peak_value = 0;
887  Point peak_point = { 0, 0 };
888  TimingMeasurement value_sum = 0;
889  TimingMeasurement time_sum = 0;
890  int points_drawn = 0;
891 
892  for (int i = 1; i < NUM_FRAMERATE_POINTS; i++) {
893  point--;
894  if (point < 0) point = NUM_FRAMERATE_POINTS - 1;
895 
896  TimingMeasurement value = durations[point];
897  if (value == PerformanceData::INVALID_DURATION) {
898  /* Skip gaps in measurements, pretend the data points on each side are continuous */
899  lastts = timestamps[point];
900  continue;
901  }
902 
903  /* Use total time period covered for value along horizontal axis */
904  time_sum += lastts - timestamps[point];
905  lastts = timestamps[point];
906  /* Stop if past the width of the graph */
907  if (time_sum > draw_horz_scale) break;
908 
909  /* Draw line from previous point to new point */
910  Point newpoint = {
911  (int)Scinterlate<int64>(x_zero, x_max, 0, (int64)draw_horz_scale, (int64)draw_horz_scale - (int64)time_sum),
912  (int)Scinterlate<int64>(y_zero, y_max, 0, (int64)draw_vert_scale, (int64)value)
913  };
914  assert(newpoint.x <= lastpoint.x);
915  GfxDrawLine(lastpoint.x, lastpoint.y, newpoint.x, newpoint.y, c_lines);
916  lastpoint = newpoint;
917 
918  /* Record peak and average value across graphed data */
919  value_sum += value;
920  points_drawn++;
921  if (value > peak_value) {
922  peak_value = value;
923  peak_point = newpoint;
924  }
925  }
926 
927  /* If the peak value is significantly larger than the average, mark and label it */
928  if (points_drawn > 0 && peak_value > TIMESTAMP_PRECISION / 100 && 2 * peak_value > 3 * value_sum / points_drawn) {
929  TextColour tc_peak = (TextColour)(TC_IS_PALETTE_COLOUR | c_peak);
930  GfxFillRect(peak_point.x - 1, peak_point.y - 1, peak_point.x + 1, peak_point.y + 1, c_peak);
931  SetDParam(0, peak_value * 1000 / TIMESTAMP_PRECISION);
932  int label_y = max(y_max, peak_point.y - FONT_HEIGHT_SMALL);
933  if (peak_point.x - x_zero > (int)this->graph_size.width / 2) {
934  DrawString(x_zero, peak_point.x - 2, label_y, STR_FRAMERATE_GRAPH_MILLISECONDS, tc_peak, SA_RIGHT | SA_FORCE, false, FS_SMALL);
935  } else {
936  DrawString(peak_point.x + 2, x_max, label_y, STR_FRAMERATE_GRAPH_MILLISECONDS, tc_peak, SA_LEFT | SA_FORCE, false, FS_SMALL);
937  }
938  }
939  }
940  }
941 };
942 
943 static WindowDesc _frametime_graph_window_desc(
944  WDP_AUTO, "frametime_graph", 140, 90,
946  0,
947  _frametime_graph_window_widgets, lengthof(_frametime_graph_window_widgets)
948 );
949 
950 
951 
954 {
955  AllocateWindowDescFront<FramerateWindow>(&_framerate_display_desc, 0);
956 }
957 
960 {
961  if (elem < PFE_FIRST || elem >= PFE_MAX) return; // maybe warn?
962  AllocateWindowDescFront<FrametimeGraphWindow>(&_frametime_graph_window_desc, elem, true);
963 }
964 
967 {
968  const int count1 = NUM_FRAMERATE_POINTS / 8;
969  const int count2 = NUM_FRAMERATE_POINTS / 4;
970  const int count3 = NUM_FRAMERATE_POINTS / 1;
971 
972  IConsolePrintF(TC_SILVER, "Based on num. data points: %d %d %d", count1, count2, count3);
973 
974  static const char *MEASUREMENT_NAMES[PFE_MAX] = {
975  "Game loop",
976  " GL station ticks",
977  " GL train ticks",
978  " GL road vehicle ticks",
979  " GL ship ticks",
980  " GL aircraft ticks",
981  " GL landscape ticks",
982  " GL link graph delays",
983  "Drawing",
984  " Viewport drawing",
985  "Video output",
986  "Sound mixing",
987  "AI/GS scripts total",
988  "Game script",
989  };
990  char ai_name_buf[128];
991 
992  static const PerformanceElement rate_elements[] = { PFE_GAMELOOP, PFE_DRAWING, PFE_VIDEO };
993 
994  bool printed_anything = false;
995 
996  for (const PerformanceElement *e = rate_elements; e < rate_elements + lengthof(rate_elements); e++) {
997  auto &pf = _pf_data[*e];
998  if (pf.num_valid == 0) continue;
999  IConsolePrintF(TC_GREEN, "%s rate: %.2ffps (expected: %.2ffps)",
1000  MEASUREMENT_NAMES[*e],
1001  pf.GetRate(),
1002  pf.expected_rate);
1003  printed_anything = true;
1004  }
1005 
1006  for (PerformanceElement e = PFE_FIRST; e < PFE_MAX; e++) {
1007  auto &pf = _pf_data[e];
1008  if (pf.num_valid == 0) continue;
1009  const char *name;
1010  if (e < PFE_AI0) {
1011  name = MEASUREMENT_NAMES[e];
1012  } else {
1013  seprintf(ai_name_buf, lastof(ai_name_buf), "AI %d %s", e - PFE_AI0 + 1, GetAIName(e - PFE_AI0)),
1014  name = ai_name_buf;
1015  }
1016  IConsolePrintF(TC_LIGHT_BLUE, "%s times: %.2fms %.2fms %.2fms",
1017  name,
1018  pf.GetAverageDurationMilliseconds(count1),
1019  pf.GetAverageDurationMilliseconds(count2),
1020  pf.GetAverageDurationMilliseconds(count3));
1021  printed_anything = true;
1022  }
1023 
1024  if (!printed_anything) {
1025  IConsoleWarning("No performance measurements have been taken yet");
1026  }
1027 }
Functions related to OTTD&#39;s strings.
Empty widget, place holder to reserve space in widget array.
Definition: widget_type.h:48
Time spent processing cargo movement.
void IConsoleWarning(const char *string)
It is possible to print warnings to the console.
Definition: console.cpp:165
Definition of stuff that is very close to a company, like the company struct itself.
virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
Update size and resize step of a widget in the window.
void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen...
Definition: gfx.cpp:113
PerformanceElement element
what element this window renders graph for
void BeginAccumulate(TimingMeasurement start_time)
Begin an accumulation of multiple measurements into a single value, from a given start time...
int vertical_scale
number of TIMESTAMP_PRECISION units vertically
Dimension graph_size
size of the main graph area (excluding axis labels)
High level window description.
Definition: window_gui.h:168
int horizontal_scale
number of half-second units horizontally
static Titem * Get(size_t index)
Returns Titem with given index.
Definition: pool_type.hpp:246
Framerate display; Window numbers:
Definition: window_type.h:688
const TimingMeasurement TIMESTAMP_PRECISION
Units a second is divided into in performance measurements
Scrollbar data structure.
Definition: widget_type.h:589
int CDECL seprintf(char *str, const char *last, const char *format,...)
Safer implementation of snprintf; same as snprintf except:
Definition: string.cpp:409
PerformanceMeasurer(PerformanceElement elem)
Begin a cycle of a measured element.
Horizontal container.
Definition: widget_type.h:75
TimingMeasurement timestamps[NUM_FRAMERATE_POINTS]
Start time of each cycle of the performance element, circular buffer.
bool Elapsed(uint delta)
Test if a timer has elapsed.
Definition: guitimer_func.h:57
AI execution for player slot 5.
Types for recording game performance data.
PerformanceElement
Elements of game performance that can be measured.
static void Paused(PerformanceElement elem)
Indicate that a cycle of "pause" where no processing occurs.
Resize box (normally at bottom-right of a window)
Definition: widget_type.h:68
void UpdateScale()
Recalculate the graph scaling factors based on current recorded data.
End of enum, must be last.
void SetExpectedRate(double rate)
Set the rate of expected cycles per second of a performance element.
Close box (at top-left of a window)
Definition: widget_type.h:69
#define lastof(x)
Get the last element of an fixed size array.
Definition: depend.cpp:50
void AddPause(TimingMeasurement start_time)
Indicate a pause/expected discontinuity in processing the element.
Game script execution.
static T max(const T a, const T b)
Returns the maximum of two values.
Definition: math_func.hpp:26
PerformanceAccumulator(PerformanceElement elem)
Begin measuring one block of the accumulating value.
Speed of drawing world and GUI.
CachedDecimal speed_gameloop
cached game loop speed factor
Time spent processing aircraft.
static void Reset(PerformanceElement elem)
Store the previous accumulator value and reset for a new cycle of accumulating measurements.
AI execution for player slot 12.
Pure simple text.
Definition: widget_type.h:58
Speed of gameloop processing.
virtual void SetStringParameters(int widget) const
Initialize string parameters for a widget.
static TimingMeasurement GetPerformanceTimer()
Return a timestamp with TIMESTAMP_PRECISION ticks per second precision.
Functions, definitions and such used only by the GUI.
void SetCapacity(int capacity)
Set the capacity of visible elements.
Definition: widget_type.h:686
void SetCount(int num)
Sets the number of elements in the list.
Definition: widget_type.h:670
Force the alignment, i.e. don&#39;t swap for RTL languages.
Definition: gfx_func.h:110
Partial widget specification to allow NWidgets to be written nested.
Definition: widget_type.h:910
Data structure for an opened window.
Definition: window_gui.h:278
static NWidgetPart SetPadding(uint8 top, uint8 right, uint8 bottom, uint8 left)
Widget part function for setting additional space around a widget.
Definition: widget_type.h:1046
void SetDParamStr(uint n, const char *str)
This function is used to "bind" a C string to a OpenTTD dparam slot.
Definition: strings.cpp:282
int num_valid
Number of data points recorded, clamped to NUM_FRAMERATE_POINTS.
Functions related to low-level strings.
static const uint8 PC_DARK_GREY
Dark grey palette colour.
Definition: gfx_func.h:208
GUI Timers.
#define FONT_HEIGHT_SMALL
Height of characters in the small (FS_SMALL) font.
Definition: gfx_func.h:177
virtual void OnClick(Point pt, int widget, int click_count)
A click with the left mouse button has been made on the window.
int GetScrolledRowFromWidget(int clickpos, const Window *const w, int widget, int padding=0, int line_height=-1) const
Compute the row of a scrolled widget that a user clicked in.
Definition: widget.cpp:1959
AI execution for player slot 2.
AI execution for player slot 9.
static bool IsValidAiID(size_t index)
Is this company a valid company, controlled by the computer (a NoAI program)?
Definition: company_base.h:137
Time spend processing road vehicles.
#define FONT_HEIGHT_NORMAL
Height of characters in the normal (FS_NORMAL) font.
Definition: gfx_func.h:180
static NWidgetPart SetDataTip(uint32 data, StringID tip)
Widget part function for setting the data and tooltip.
Definition: widget_type.h:1014
Functions related to the gfx engine.
void CDECL IConsolePrintF(TextColour colour_code, const char *format,...)
Handle the printing of text entered into the console or redirected there by any other means...
Definition: console.cpp:132
AI execution for player slot 15.
Center both horizontally and vertically.
Definition: gfx_func.h:108
virtual void DrawWidget(const Rect &r, int widget) const
Draw the contents of a nested widget.
A number of safeguards to prevent using unsafe methods.
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition: gfx_type.h:247
AI execution for player slot 7.
AI execution for player slot 6.
Simple depressed panel.
Definition: widget_type.h:50
void ShowFramerateWindow()
Open the general framerate window.
AI execution for player slot 14.
static const uint MILLISECONDS_PER_TICK
The number of milliseconds per game tick.
Definition: gfx_type.h:306
~PerformanceMeasurer()
Finish a cycle of a measured element and store the measurement taken.
Console functions used outside of the console code.
void ConPrintFramerate()
Print performance statistics to game console.
PerformanceData(double expected_rate)
Initialize a data element with an expected collection rate.
void ShowFrametimeGraphWindow(PerformanceElement elem)
Open a graph window for a performance element.
virtual void DrawWidget(const Rect &r, int widget) const
Draw the contents of a nested widget.
const int NUM_FRAMERATE_POINTS
Number of data points to keep in buffer for each performance measurement.
static NWidgetPart NWidget(WidgetType tp, Colours col, int16 idx=-1)
Widget part function for starting a new &#39;real&#39; widget.
Definition: widget_type.h:1114
int DrawString(int left, int right, int top, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition: gfx.cpp:500
Time spent processing other world features.
#define lengthof(x)
Return the length of an fixed size array.
Definition: depend.cpp:42
static T min(const T a, const T b)
Returns the minimum of two values.
Definition: math_func.hpp:42
Frame time graph; Window numbers:
Definition: window_type.h:694
GUITimer next_scale_update
interval for next scale update
uint32 StringID
Numeric value that represents a string, independent of the selected language.
Definition: strings_type.h:18
static const uint8 PC_BLACK
Black palette colour.
Definition: gfx_func.h:207
double expected_rate
Expected number of cycles per second when the system is running without slowdowns.
AI execution for player slot 1.
int prev_index
Last index written to in durations and timestamps.
uint64 TimingMeasurement
Type used to hold a performance timing measurement.
void AddAccumulate(TimingMeasurement duration)
Accumulate a period onto the current measurement.
Dimension GetStringBoundingBox(const char *str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition: gfx.cpp:700
CachedDecimal rate_drawing
cached drawing frame rate
No window, redirects to WC_MAIN_WINDOW.
Definition: window_type.h:40
virtual void OnResize()
Called after the window got resized.
static const double GL_RATE
Game loop rate, cycles per second
static const uint8 PC_DARK_RED
Dark red palette colour.
Definition: gfx_func.h:213
void DrawElementTimesColumn(const Rect &r, StringID heading_str, const CachedDecimal *values) const
Render a column of formatted average durations.
AI execution for player slot 10.
Window caption (window title between closebox and stickybox)
Definition: widget_type.h:61
Time spent processing ships.
Time spent drawing world viewports in GUI.
Speed of painting drawn video buffer.
AI execution for player slot 8.
virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
Update size and resize step of a widget in the window.
Vertical container.
Definition: widget_type.h:77
static NWidgetPart EndContainer()
Widget part function for denoting the end of a container (horizontal, vertical, WWT_FRAME, WWT_INSET, or WWT_PANEL).
Definition: widget_type.h:999
~PerformanceAccumulator()
Finish and add one block of the accumulating value.
AI execution for player slot 3.
AI execution for player slot 11.
Coordinates of a point in 2D.
Index of the small font in the font tables.
Definition: gfx_type.h:205
Globally used console related types.
Types related to the framerate windows widgets.
Colour value is already a real palette colour index, not an index of a StringColour.
Definition: gfx_type.h:270
void Add(TimingMeasurement start_time, TimingMeasurement end_time)
Collect a complete measurement, given start and ending times for a processing block.
double GetAverageDurationMilliseconds(int count)
Get average cycle processing time over a number of data points.
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition: widget_type.h:66
int32 WindowNumber
Number to differentiate different windows of the same class.
Definition: window_type.h:707
Specification of a rectangle with absolute coordinates of all edges.
Vertical scrollbar.
Definition: widget_type.h:84
Right align the text (must be a single bit).
Definition: gfx_func.h:100
Left align the text.
Definition: gfx_func.h:98
AI execution for player slot 13.
int next_index
Next index to write to in durations and timestamps.
Window functions not directly related to making/drawing windows.
TimingMeasurement durations[NUM_FRAMERATE_POINTS]
Time spent processing each cycle of the performance element, circular buffer.
AIInfo keeps track of all information of an AI, like Author, Description, ...
Find a place automatically.
Definition: window_gui.h:156
PerformanceData _pf_data[PFE_MAX]
Storage for all performance element measurements.
void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen)
Resize the window.
Definition: window.cpp:2126
static NWidgetPart SetScrollbar(int index)
Attach a scrollbar to a widget.
Definition: widget_type.h:1095
virtual void OnRealtimeTick(uint delta_ms)
Called periodically.
Dimensions (a width and height) of a rectangle in 2D.
This file contains all sprite-related enums and defines.
static void SetInactive(PerformanceElement elem)
Mark a performance element as not currently in use.
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition: widget_type.h:64
Time spent processing trains.
virtual void SetStringParameters(int widget) const
Initialize string parameters for a widget.
Time spent waiting for link graph background jobs.
double GetRate()
Get current rate of a performance element, based on approximately the past one second of data...
TimingMeasurement acc_timestamp
Start time for current accumulation cycle.
static NWidgetPart SetPIP(uint8 pre, uint8 inter, uint8 post)
Widget part function for setting a pre/inter/post spaces.
Definition: widget_type.h:1076
TimingMeasurement acc_duration
Current accumulated duration.
CachedDecimal rate_gameloop
cached game loop tick rate
AI execution for player slot 4.
uint16 GetPosition() const
Gets the position of the first visible element in the list.
Definition: widget_type.h:631
virtual void OnRealtimeTick(uint delta_ms)
Called periodically.
Sum of all GS/AI scripts.
static void SetDParam(uint n, uint64 v)
Set a string parameter v at index n in the global string parameter array.
Definition: strings_func.h:201
Speed of mixing audio samples.