Fix infinite loop detection when placing players randomly on the newer random map...
[0ad.git] / source / ps / ProfileViewer.cpp
blob4f7ece40c799a944bfd5ac4c364b2a1a51354f5a
1 /* Copyright (C) 2015 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
19 * Implementation of profile display (containing only display routines,
20 * the data model(s) are implemented elsewhere).
23 #include "precompiled.h"
25 #include <ctime>
26 #include <algorithm>
28 #include "ProfileViewer.h"
30 #include "graphics/FontMetrics.h"
31 #include "gui/GUIutil.h"
32 #include "graphics/ShaderManager.h"
33 #include "graphics/TextRenderer.h"
34 #include "ps/CLogger.h"
35 #include "ps/Filesystem.h"
36 #include "ps/Hotkey.h"
37 #include "ps/Profile.h"
38 #include "lib/external_libraries/libsdl.h"
39 #include "renderer/Renderer.h"
40 #include "scriptinterface/ScriptInterface.h"
42 extern int g_xres, g_yres;
44 struct CProfileViewerInternals
46 NONCOPYABLE(CProfileViewerInternals); // because of the ofstream
47 public:
48 CProfileViewerInternals() {}
50 /// Whether the profiling display is currently visible
51 bool profileVisible;
53 /// List of root tables
54 std::vector<AbstractProfileTable*> rootTables;
56 /// Path from a root table (path[0]) to the currently visible table (path[size-1])
57 std::vector<AbstractProfileTable*> path;
59 /// Helper functions
60 void TableIsDeleted(AbstractProfileTable* table);
61 void NavigateTree(int id);
63 /// File for saved profile output (reset when the game is restarted)
64 std::ofstream outputStream;
68 ///////////////////////////////////////////////////////////////////////////////////////////////
69 // AbstractProfileTable implementation
71 AbstractProfileTable::~AbstractProfileTable()
73 if (CProfileViewer::IsInitialised())
75 g_ProfileViewer.m->TableIsDeleted(this);
80 ///////////////////////////////////////////////////////////////////////////////////////////////
81 // CProfileViewer implementation
84 // AbstractProfileTable got deleted, make sure we have no dangling pointers
85 void CProfileViewerInternals::TableIsDeleted(AbstractProfileTable* table)
87 for(int idx = (int)rootTables.size()-1; idx >= 0; --idx)
89 if (rootTables[idx] == table)
90 rootTables.erase(rootTables.begin() + idx);
93 for(size_t idx = 0; idx < path.size(); ++idx)
95 if (path[idx] != table)
96 continue;
98 path.erase(path.begin() + idx, path.end());
99 if (path.size() == 0)
100 profileVisible = false;
105 // Move into child tables or return to parent tables based on the given number
106 void CProfileViewerInternals::NavigateTree(int id)
108 if (id == 0)
110 if (path.size() > 1)
111 path.pop_back();
113 else
115 AbstractProfileTable* table = path[path.size() - 1];
116 size_t numrows = table->GetNumberRows();
118 for(size_t row = 0; row < numrows; ++row)
120 AbstractProfileTable* child = table->GetChild(row);
122 if (!child)
123 continue;
125 --id;
126 if (id == 0)
128 path.push_back(child);
129 break;
136 // Construction/Destruction
137 CProfileViewer::CProfileViewer()
139 m = new CProfileViewerInternals;
140 m->profileVisible = false;
143 CProfileViewer::~CProfileViewer()
145 delete m;
149 // Render
150 void CProfileViewer::RenderProfile()
152 if (!m->profileVisible)
153 return;
155 if (!m->path.size())
157 m->profileVisible = false;
158 return;
161 PROFILE3_GPU("profile viewer");
163 glEnable(GL_BLEND);
164 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
166 AbstractProfileTable* table = m->path[m->path.size() - 1];
167 const std::vector<ProfileColumn>& columns = table->GetColumns();
168 size_t numrows = table->GetNumberRows();
170 CStrIntern font_name("mono-stroke-10");
171 CFontMetrics font(font_name);
172 int lineSpacing = font.GetLineSpacing();
174 // Render background
175 GLint estimate_height;
176 GLint estimate_width;
178 estimate_width = 50;
179 for(size_t i = 0; i < columns.size(); ++i)
180 estimate_width += (GLint)columns[i].width;
182 estimate_height = 3 + (GLint)numrows;
183 if (m->path.size() > 1)
184 estimate_height += 2;
185 estimate_height = lineSpacing*estimate_height;
187 CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
188 solidTech->BeginPass();
189 CShaderProgramPtr solidShader = solidTech->GetShader();
191 solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.5f);
193 CMatrix3D transform = GetDefaultGuiMatrix();
194 solidShader->Uniform(str_transform, transform);
196 float backgroundVerts[] = {
197 (float)estimate_width, 0.0f,
198 0.0f, 0.0f,
199 0.0f, (float)estimate_height,
200 0.0f, (float)estimate_height,
201 (float)estimate_width, (float)estimate_height,
202 (float)estimate_width, 0.0f
204 solidShader->VertexPointer(2, GL_FLOAT, 0, backgroundVerts);
205 solidShader->AssertPointersBound();
206 glDrawArrays(GL_TRIANGLES, 0, 6);
208 transform.PostTranslate(22.0f, lineSpacing*3.0f, 0.0f);
209 solidShader->Uniform(str_transform, transform);
211 // Draw row backgrounds
212 for (size_t row = 0; row < numrows; ++row)
214 if (row % 2)
215 solidShader->Uniform(str_color, 1.0f, 1.0f, 1.0f, 0.1f);
216 else
217 solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.1f);
219 float rowVerts[] = {
220 -22.f, 2.f,
221 estimate_width-22.f, 2.f,
222 estimate_width-22.f, 2.f-lineSpacing,
224 estimate_width-22.f, 2.f-lineSpacing,
225 -22.f, 2.f-lineSpacing,
226 -22.f, 2.f
228 solidShader->VertexPointer(2, GL_FLOAT, 0, rowVerts);
229 solidShader->AssertPointersBound();
230 glDrawArrays(GL_TRIANGLES, 0, 6);
232 transform.PostTranslate(0.0f, lineSpacing, 0.0f);
233 solidShader->Uniform(str_transform, transform);
236 solidTech->EndPass();
238 // Print table and column titles
240 CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
241 textTech->BeginPass();
243 CTextRenderer textRenderer(textTech->GetShader());
244 textRenderer.Font(font_name);
245 textRenderer.Color(1.0f, 1.0f, 1.0f);
247 textRenderer.PrintfAt(2.0f, lineSpacing, L"%hs", table->GetTitle().c_str());
249 textRenderer.Translate(22.0f, lineSpacing*2.0f, 0.0f);
251 float colX = 0.0f;
252 for (size_t col = 0; col < columns.size(); ++col)
254 CStrW text = columns[col].title.FromUTF8();
255 int w, h;
256 font.CalculateStringSize(text.c_str(), w, h);
258 float x = colX;
259 if (col > 0) // right-align all but the first column
260 x += columns[col].width - w;
261 textRenderer.Put(x, 0.0f, text.c_str());
263 colX += columns[col].width;
266 textRenderer.Translate(0.0f, lineSpacing, 0.0f);
268 // Print rows
269 int currentExpandId = 1;
271 for (size_t row = 0; row < numrows; ++row)
273 if (table->IsHighlightRow(row))
274 textRenderer.Color(1.0f, 0.5f, 0.5f);
275 else
276 textRenderer.Color(1.0f, 1.0f, 1.0f);
278 if (table->GetChild(row))
280 textRenderer.PrintfAt(-15.0f, 0.0f, L"%d", currentExpandId);
281 currentExpandId++;
284 float colX = 0.0f;
285 for (size_t col = 0; col < columns.size(); ++col)
287 CStrW text = table->GetCellText(row, col).FromUTF8();
288 int w, h;
289 font.CalculateStringSize(text.c_str(), w, h);
291 float x = colX;
292 if (col > 0) // right-align all but the first column
293 x += columns[col].width - w;
294 textRenderer.Put(x, 0.0f, text.c_str());
296 colX += columns[col].width;
299 textRenderer.Translate(0.0f, lineSpacing, 0.0f);
302 textRenderer.Color(1.0f, 1.0f, 1.0f);
304 if (m->path.size() > 1)
306 textRenderer.Translate(0.0f, lineSpacing, 0.0f);
307 textRenderer.Put(-15.0f, 0.0f, L"0");
308 textRenderer.Put(0.0f, 0.0f, L"back to parent");
311 textRenderer.Render();
312 textTech->EndPass();
314 glDisable(GL_BLEND);
316 glEnable(GL_DEPTH_TEST);
320 // Handle input
321 InReaction CProfileViewer::Input(const SDL_Event_* ev)
323 switch(ev->ev.type)
325 case SDL_KEYDOWN:
327 if (!m->profileVisible)
328 break;
330 int k = ev->ev.key.keysym.sym;
331 if (k >= SDLK_0 && k <= SDLK_9)
333 m->NavigateTree(k - SDLK_0);
334 return IN_HANDLED;
336 break;
338 case SDL_HOTKEYDOWN:
339 std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
341 if( hotkey == "profile.toggle" )
343 if (!m->profileVisible)
345 if (m->rootTables.size())
347 m->profileVisible = true;
348 m->path.push_back(m->rootTables[0]);
351 else
353 size_t i;
355 for(i = 0; i < m->rootTables.size(); ++i)
357 if (m->rootTables[i] == m->path[0])
358 break;
360 i++;
362 m->path.clear();
363 if (i < m->rootTables.size())
365 m->path.push_back(m->rootTables[i]);
367 else
369 m->profileVisible = false;
372 return( IN_HANDLED );
374 else if( hotkey == "profile.save" )
376 SaveToFile();
377 return( IN_HANDLED );
379 break;
381 return( IN_PASS );
384 InReaction CProfileViewer::InputThunk(const SDL_Event_* ev)
386 if (CProfileViewer::IsInitialised())
387 return g_ProfileViewer.Input(ev);
389 return IN_PASS;
393 // Add a table to the list of roots
394 void CProfileViewer::AddRootTable(AbstractProfileTable* table, bool front)
396 if (front)
397 m->rootTables.insert(m->rootTables.begin(), table);
398 else
399 m->rootTables.push_back(table);
402 namespace
404 struct WriteTable
406 std::ofstream& f;
407 WriteTable(std::ofstream& f) : f(f) {}
409 void operator() (AbstractProfileTable* table)
411 std::vector<CStr> data; // 2d array of (rows+head)*columns elements
413 const std::vector<ProfileColumn>& columns = table->GetColumns();
415 // Add column headers to 'data'
416 for (std::vector<ProfileColumn>::const_iterator col_it = columns.begin();
417 col_it != columns.end(); ++col_it)
418 data.push_back(col_it->title);
420 // Recursively add all profile data to 'data'
421 WriteRows(1, table, data);
423 // Calculate the width of each column ( = the maximum width of
424 // any value in that column)
425 std::vector<size_t> columnWidths;
426 size_t cols = columns.size();
427 for (size_t c = 0; c < cols; ++c)
429 size_t max = 0;
430 for (size_t i = c; i < data.size(); i += cols)
431 max = std::max(max, data[i].length());
432 columnWidths.push_back(max);
435 // Output data as a formatted table:
437 f << "\n\n" << table->GetTitle() << "\n";
439 if (cols == 0) // avoid divide-by-zero
440 return;
442 for (size_t r = 0; r < data.size()/cols; ++r)
444 for (size_t c = 0; c < cols; ++c)
445 f << (c ? " | " : "\n")
446 << data[r*cols + c].Pad(PS_TRIM_RIGHT, columnWidths[c]);
448 // Add dividers under some rows. (Currently only the first, since
449 // that contains the column headers.)
450 if (r == 0)
451 for (size_t c = 0; c < cols; ++c)
452 f << (c ? "-|-" : "\n")
453 << CStr::Repeat("-", columnWidths[c]);
457 void WriteRows(int indent, AbstractProfileTable* table, std::vector<CStr>& data)
459 const std::vector<ProfileColumn>& columns = table->GetColumns();
461 for (size_t r = 0; r < table->GetNumberRows(); ++r)
463 // Do pretty tree-structure indenting
464 CStr indentation = CStr::Repeat("| ", indent-1);
465 if (r+1 == table->GetNumberRows())
466 indentation += "'-";
467 else
468 indentation += "|-";
470 for (size_t c = 0; c < columns.size(); ++c)
471 if (c == 0)
472 data.push_back(indentation + table->GetCellText(r, c));
473 else
474 data.push_back(table->GetCellText(r, c));
476 if (table->GetChild(r))
477 WriteRows(indent+1, table->GetChild(r), data);
481 private:
482 const WriteTable& operator=(const WriteTable&);
485 struct DumpTable
487 ScriptInterface& m_ScriptInterface;
488 JS::PersistentRooted<JS::Value> m_Root;
489 DumpTable(ScriptInterface& scriptInterface, JS::HandleValue root) :
490 m_ScriptInterface(scriptInterface), m_Root(scriptInterface.GetJSRuntime(), root)
494 // std::for_each requires a move constructor and the use of JS::PersistentRooted<T> apparently breaks a requirement for an
495 // automatic move constructor
496 DumpTable(DumpTable && original) :
497 m_ScriptInterface(original.m_ScriptInterface),
498 m_Root(original.m_ScriptInterface.GetJSRuntime(), original.m_Root.get())
502 void operator() (AbstractProfileTable* table)
504 JSContext* cx = m_ScriptInterface.GetContext();
505 JSAutoRequest rq(cx);
507 JS::RootedValue t(cx);
508 JS::RootedValue rows(cx, DumpRows(table));
509 m_ScriptInterface.Eval(L"({})", &t);
510 m_ScriptInterface.SetProperty(t, "cols", DumpCols(table));
511 m_ScriptInterface.SetProperty(t, "data", rows);
513 m_ScriptInterface.SetProperty(m_Root, table->GetTitle().c_str(), t);
516 std::vector<std::string> DumpCols(AbstractProfileTable* table)
518 std::vector<std::string> titles;
520 const std::vector<ProfileColumn>& columns = table->GetColumns();
522 for (size_t c = 0; c < columns.size(); ++c)
523 titles.push_back(columns[c].title);
525 return titles;
528 JS::Value DumpRows(AbstractProfileTable* table)
530 JSContext* cx = m_ScriptInterface.GetContext();
531 JSAutoRequest rq(cx);
533 JS::RootedValue data(cx);
534 m_ScriptInterface.Eval("({})", &data);
536 const std::vector<ProfileColumn>& columns = table->GetColumns();
538 for (size_t r = 0; r < table->GetNumberRows(); ++r)
540 JS::RootedValue row(cx);
541 m_ScriptInterface.Eval("([])", &row);
542 m_ScriptInterface.SetProperty(data, table->GetCellText(r, 0).c_str(), row);
544 if (table->GetChild(r))
546 JS::RootedValue childRows(cx, DumpRows(table->GetChild(r)));
547 m_ScriptInterface.SetPropertyInt(row, 0, childRows);
550 for (size_t c = 1; c < columns.size(); ++c)
551 m_ScriptInterface.SetPropertyInt(row, c, table->GetCellText(r, c));
554 return data;
557 private:
558 const DumpTable& operator=(const DumpTable&);
561 bool SortByName(AbstractProfileTable* a, AbstractProfileTable* b)
563 return (a->GetName() < b->GetName());
567 void CProfileViewer::SaveToFile()
569 // Open the file, if necessary. If this method is called several times,
570 // the profile results will be appended to the previous ones from the same
571 // run.
572 if (! m->outputStream.is_open())
574 // Open the file. (It will be closed when the CProfileViewer
575 // destructor is called.)
576 OsPath path = psLogDir()/"profile.txt";
577 m->outputStream.open(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
579 if (m->outputStream.fail())
581 LOGERROR("Failed to open profile log file");
582 return;
584 else
586 LOGMESSAGERENDER("Profiler snapshot saved to '%s'", path.string8());
590 time_t t;
591 time(&t);
592 m->outputStream << "================================================================\n\n";
593 m->outputStream << "PS profiler snapshot - " << asctime(localtime(&t));
595 std::vector<AbstractProfileTable*> tables = m->rootTables;
596 sort(tables.begin(), tables.end(), SortByName);
597 for_each(tables.begin(), tables.end(), WriteTable(m->outputStream));
599 m->outputStream << "\n\n================================================================\n";
600 m->outputStream.flush();
603 JS::Value CProfileViewer::SaveToJS(ScriptInterface& scriptInterface)
605 JSContext* cx = scriptInterface.GetContext();
606 JSAutoRequest rq(cx);
608 JS::RootedValue root(cx);
609 scriptInterface.Eval("({})", &root);
611 std::vector<AbstractProfileTable*> tables = m->rootTables;
612 sort(tables.begin(), tables.end(), SortByName);
613 for_each(tables.begin(), tables.end(), DumpTable(scriptInterface, root));
615 return root;
618 void CProfileViewer::ShowTable(const CStr& table)
620 m->path.clear();
622 if (table.length() > 0)
624 for (size_t i = 0; i < m->rootTables.size(); ++i)
626 if (m->rootTables[i]->GetName() == table)
628 m->path.push_back(m->rootTables[i]);
629 m->profileVisible = true;
630 return;
635 // No matching table found, so don't display anything
636 m->profileVisible = false;