Skip to content

Commit 1efa249

Browse files
committed
GPU Display Console: Add the possibility to execute a script file
1 parent 6d223b2 commit 1efa249

7 files changed

Lines changed: 161 additions & 73 deletions

File tree

‎GPU/GPUTracking/Definitions/GPUSettingsList.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ AddOption(listFrontendOptionHandling, bool, false, "", 0, "List all assigned key
529529
AddOption(consoleDelay, float, 0.5f, "", 0, "Time in seconds to open / close the console (^ key)")
530530
AddOption(consoleAlpha, float, 0.8f, "", 0, "Opacity of the console background")
531531
AddOption(consoleVertical, float, 0.5f, "", 0, "Fraction of the window height covered by the fully opened console")
532+
AddOption(runScript, std::string, "", "", 0, "Console script file, run before the first frame of the display")
532533
AddOption(consoleHistorySize, uint32_t, 1000000, "", 0, "Number of console lines kept, the history is shortened to this when it reaches twice the size")
533534
AddSubConfig(GPUSettingsDisplayLight, light)
534535
AddSubConfig(GPUSettingsDisplayHeavy, heavy)

‎GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx‎

Lines changed: 102 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
/// \author David Rohr
1414

1515
#include "GPUDisplayConsole.h"
16-
#include "GPULogging.h"
1716
#include <algorithm>
1817
#include <charconv>
1918
#include <stdexcept>
2019
#include <cstdarg>
2120
#include <cstdio>
2221
#include <cstring>
22+
#include <fstream>
23+
#include <sstream>
2324

2425
using namespace o2::gpu;
2526

@@ -124,30 +125,19 @@ void GPUDisplayConsole::consoleLimitHistory(uint32_t historySize)
124125

125126
// Tokens separated by spaces: text in single / double quotes and characters after a backslash are plain text (the token is a string),
126127
// otherwise a token is int64_t for [+-]digits, double for [+-]digits with one '.', a string otherwise. Throws std::runtime_error for an unterminated quote / trailing backslash.
127-
std::vector<GPUDisplayConsole::consoleArg> GPUDisplayConsole::parseConsoleCommand(const std::string& cmd)
128+
129+
std::tuple<const GPUDisplayConsole::GPUDisplayConsoleCommand&, std::vector<GPUDisplayConsole::consoleArg>> GPUDisplayConsole::parseConsoleCommand(std::string_view cmd) const
128130
{
129-
std::vector<consoleArg> retVal;
131+
std::vector<std::pair<std::string, bool>> tokens; // Text, plain text (quoted / escaped)
130132
std::string token;
131133
bool inToken = false, plainText = false, escape = false;
132134
char quote = 0;
133135
auto finishToken = [&]() {
134-
if (!inToken) {
135-
return;
136+
if (inToken) {
137+
tokens.emplace_back(std::move(token), plainText);
138+
token.clear();
139+
inToken = plainText = false;
136140
}
137-
const char* begin = token.c_str() + (token[0] == '+' || token[0] == '-');
138-
const size_t nDigits = std::count_if(begin, token.c_str() + token.size(), [](char c) { return c >= '0' && c <= '9'; });
139-
const size_t nDots = std::count(begin, token.c_str() + token.size(), '.');
140-
const bool number = !plainText && nDigits && nDigits + nDots == (size_t)(token.c_str() + token.size() - begin);
141-
int64_t intValue = 0;
142-
if (number && nDots == 0 && std::from_chars(begin, token.c_str() + token.size(), intValue).ec == std::errc()) {
143-
retVal.emplace_back(token[0] == '-' ? -intValue : intValue);
144-
} else if (number && nDots <= 1) {
145-
retVal.emplace_back(std::stod(token)); // Also integers out of the int64_t range
146-
} else {
147-
retVal.emplace_back(token);
148-
}
149-
token.clear();
150-
inToken = plainText = false;
151141
};
152142
for (char c : cmd) {
153143
if (escape) {
@@ -175,26 +165,110 @@ std::vector<GPUDisplayConsole::consoleArg> GPUDisplayConsole::parseConsoleComman
175165
throw std::runtime_error(quote ? "unterminated quote" : "backslash at the end");
176166
}
177167
finishToken();
178-
return retVal;
168+
if (tokens.empty()) {
169+
throw std::runtime_error("empty command");
170+
}
171+
172+
const std::string& name = tokens[0].first;
173+
const auto it = mConsoleCommands.find(name);
174+
if (it == mConsoleCommands.end()) {
175+
throw std::runtime_error("unknown command " + name);
176+
}
177+
const std::string& types = it->second.args;
178+
const bool more = types.size() && types.back() == '+';
179+
const size_t nFixed = types.size() - more, nArgs = tokens.size() - 1;
180+
if (nArgs < nFixed || (!more && nArgs > nFixed)) {
181+
throw std::runtime_error(name + ": " + std::to_string(nFixed) + (more ? " or more" : "") + " arguments expected, got " + std::to_string(nArgs));
182+
}
183+
auto autoType = [](const std::string& text, bool plain) -> consoleArg { // int64_t for [+-]digits, double for [+-]digits with one '.', string otherwise or if plain text
184+
const char *begin = text.c_str() + (text[0] == '+' || text[0] == '-'), *end = text.c_str() + text.size();
185+
const size_t nDigits = std::count_if(begin, end, [](char c) { return c >= '0' && c <= '9'; });
186+
const size_t nDots = std::count(begin, end, '.');
187+
const bool number = !plain && nDigits && nDigits + nDots == (size_t)(end - begin);
188+
int64_t intValue = 0;
189+
if (number && nDots == 0 && std::from_chars(begin, end, intValue).ec == std::errc()) {
190+
return text[0] == '-' ? -intValue : intValue;
191+
} else if (number && nDots <= 1) {
192+
return std::stod(text); // Also integers out of the int64_t range
193+
}
194+
return text;
195+
};
196+
std::vector<consoleArg> args;
197+
for (size_t i = 0; i < nArgs; i++) {
198+
const auto& [text, plain] = tokens[i + 1];
199+
const char type = i < nFixed ? types[i] : '+';
200+
consoleArg value = type == 's' ? consoleArg(text) : autoType(text, plain);
201+
if (type == 'i' && !std::holds_alternative<int64_t>(value)) {
202+
throw std::runtime_error(name + ": argument " + std::to_string(i + 1) + " (" + text + ") must be an integer");
203+
} else if (type == 'd') {
204+
if (std::holds_alternative<int64_t>(value)) {
205+
value = (double)std::get<int64_t>(value);
206+
} else if (!std::holds_alternative<double>(value)) {
207+
throw std::runtime_error(name + ": argument " + std::to_string(i + 1) + " (" + text + ") must be a number");
208+
}
209+
} else if (type != 's' && type != 'i' && type != '+') {
210+
throw std::runtime_error(name + ": invalid argument types " + types);
211+
}
212+
args.emplace_back(std::move(value));
213+
}
214+
return {it->second, std::move(args)};
179215
}
180216

181-
void GPUDisplayConsole::handleConsoleCommand(const std::string& cmd)
217+
void GPUDisplayConsole::handleConsoleCommand(std::string_view cmd)
182218
{
219+
if (cmd.find_first_not_of(' ') == std::string_view::npos) { // Empty line
220+
return;
221+
}
222+
const GPUDisplayConsoleCommand* command = nullptr;
183223
std::vector<consoleArg> args;
184224
try {
185-
args = parseConsoleCommand(cmd);
225+
auto parsed = parseConsoleCommand(cmd);
226+
command = &std::get<0>(parsed);
227+
args = std::move(std::get<1>(parsed));
186228
} catch (const std::runtime_error& e) {
187229
consolePrint("Invalid command: %s", e.what());
188230
return;
189231
}
190-
if (args.empty()) {
232+
command->func(args);
233+
}
234+
235+
void GPUDisplayConsole::runScript(std::string_view script)
236+
{
237+
if (mScriptDepth >= 16) {
238+
consolePrint("Scripts nested too deeply");
191239
return;
192240
}
193-
const std::string* name = std::get_if<std::string>(&args[0]);
194-
const auto it = name ? mConsoleCommands.find(*name) : mConsoleCommands.end();
195-
if (it == mConsoleCommands.end()) {
196-
consolePrint("Unknown command: %s", cmd.c_str());
241+
mScriptDepth++;
242+
for (size_t pos = 0; pos < script.size();) {
243+
const size_t end = std::min(script.find_first_of("\r\n", pos), script.size());
244+
handleConsoleCommand(script.substr(pos, end - pos)); // Empty lines (consecutive \r / \n) are ignored
245+
pos = end + 1;
246+
}
247+
mScriptDepth--;
248+
}
249+
250+
void GPUDisplayConsole::runScriptFile(const std::string& filename)
251+
{
252+
std::ifstream file(filename, std::ios::binary);
253+
if (!file) {
254+
consolePrint("Cannot open script file %s", filename.c_str());
197255
return;
198256
}
199-
it->second(std::span(args).subspan(1));
257+
std::stringstream content;
258+
content << file.rdbuf();
259+
runScript(content.str());
260+
}
261+
262+
void GPUDisplayConsole::printCommands(const std::function<void(const std::string&)>& print)
263+
{
264+
std::vector<std::string> names;
265+
for (const auto& [name, command] : mConsoleCommands) {
266+
names.emplace_back(name);
267+
}
268+
std::sort(names.begin(), names.end());
269+
print("Console commands (argument types: s string, i integer, d number, + further arguments):");
270+
for (const auto& name : names) {
271+
const auto& command = mConsoleCommands[name];
272+
print(" " + name + (command.args.empty() ? "" : " ") + command.args + ": " + command.description);
273+
}
200274
}

‎GPU/GPUTracking/display/frontend/GPUDisplayConsole.h‎

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include <functional>
2222
#include <span>
2323
#include <string>
24+
#include <string_view>
25+
#include <tuple>
2426
#include <unordered_map>
2527
#include <variant>
2628
#include <vector>
@@ -31,24 +33,33 @@ class GPUDisplayConsole : public GPUDisplayFrontendKeyVars // Text console of th
3133
{
3234
public:
3335
using consoleArg = std::variant<int64_t, double, std::string>; // Argument of a console command
34-
GPUDisplayConsole(const std::string& title); // First history entry, not selectable
36+
struct GPUDisplayConsoleCommand {
37+
std::function<void(std::span<const consoleArg>)> func;
38+
std::string args; // One character per argument: s string, i int64_t, d double (also from an integer), + at the end: any further arguments (type from the text)
39+
std::string description; // For help
40+
};
41+
GPUDisplayConsole(const std::string& title); // First history entry, not selectable
3542

36-
void handleConsoleKey(uint8_t key); // Key typed while the console is shown
37-
void handleConsoleCommand(const std::string& cmd); // Runs the command named by the first token, error if unknown
38-
static std::vector<consoleArg> parseConsoleCommand(const std::string& cmd); // Split into arguments, throws std::runtime_error for invalid input
39-
void consolePrint(const char* format, ...); // printf-like output to the console, indented by 2 spaces in mConsoleHistory
40-
void consoleInfo(const char* format, const char* text); // Output of SetInfo, updates the last line instead for the same message within 2 s
41-
void consoleAppend(std::string line); // Every other new line of the console
42-
void consoleLimitHistory(uint32_t historySize); // Once per frame: keeps historySize lines when the history reaches twice that
43+
void handleConsoleKey(uint8_t key); // Key typed while the console is shown
44+
void handleConsoleCommand(std::string_view cmd); // Runs the command named by the first token, error if unknown
45+
void runScript(std::string_view script); // Runs each line (separated by \n / \r) as console command
46+
void runScriptFile(const std::string& filename); // runScript with the content of the file
47+
void printCommands(const std::function<void(const std::string&)>& print); // List of the commands with argument types and description, one line per call
48+
std::tuple<const GPUDisplayConsoleCommand&, std::vector<consoleArg>> parseConsoleCommand(std::string_view cmd) const; // Command and its arguments with the types it requests, throws std::runtime_error for syntax errors, unknown commands, wrong arguments
49+
void consolePrint(const char* format, ...); // printf-like output to the console, indented by 2 spaces in mConsoleHistory
50+
void consoleInfo(const char* format, const char* text); // Output of SetInfo, updates the last line instead for the same message within 2 s
51+
void consoleAppend(std::string line); // Every other new line of the console
52+
void consoleLimitHistory(uint32_t historySize); // Once per frame: keeps historySize lines when the history reaches twice that
4353
const std::string& shownText() const { return mConsoleTextCurrent ? mConsoleHistory[mConsoleTextCurrent] : mConsoleText; }
4454

45-
std::unordered_map<std::string, std::function<void(std::span<const consoleArg>)>> mConsoleCommands; // Console commands by name
46-
std::string mConsoleInfoLastMessage; // Format of the SetInfo that wrote the last console line, empty if something else came after it
47-
std::chrono::steady_clock::time_point mConsoleInfoLastTime; //
48-
std::vector<std::string> mConsoleHistory; // Title, then commands entered in the console and their output (indented)
49-
std::string mConsoleText; // Text being typed in the console
50-
uint32_t mConsoleTextCurrent = 0; // Entry of mConsoleHistory shown in the console (selected with up / down, the title entry 0 is not selectable), 0 for mConsoleText
51-
uint32_t mConsoleTextPos = 0; // Cursor position in the shown text (left / right), the character there is the current one
55+
std::unordered_map<std::string, GPUDisplayConsoleCommand> mConsoleCommands; // Console commands by name, filled by GPUDisplayFrontend::buildConsoleCommands
56+
uint32_t mScriptDepth = 0; // Scripts running scripts
57+
std::string mConsoleInfoLastMessage; // Format of the SetInfo that wrote the last console line, empty if something else came after it
58+
std::chrono::steady_clock::time_point mConsoleInfoLastTime; //
59+
std::vector<std::string> mConsoleHistory; // Title, then commands entered in the console and their output (indented)
60+
std::string mConsoleText; // Text being typed in the console
61+
uint32_t mConsoleTextCurrent = 0; // Entry of mConsoleHistory shown in the console (selected with up / down, the title entry 0 is not selectable), 0 for mConsoleText
62+
uint32_t mConsoleTextPos = 0; // Cursor position in the shown text (left / right), the character there is the current one
5263
};
5364
} // namespace o2::gpu
5465

‎GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,13 @@ int32_t GPUDisplayFrontend::HandleUpdatesAndDrawGLScene(bool suspended)
192192
{
193193
HandleExternalGUISignals();
194194
mDisplay->checkAndApplyConfigChanges();
195-
const int32_t retVal = mDisplay->DrawGLScene(suspended);
196-
mDisplay->mOldCfg = {mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR}; // Also includes the changes during drawing (animation, scene reset, ...), which set their update flags themselves
195+
const bool startupScript = !mStartupScriptDone && !mDisplay->cfg().runScript.empty();
196+
const int32_t retVal = mDisplay->DrawGLScene(suspended || startupScript); // Before the startup script, the first frame only loads the event, it is not rendered
197+
mDisplay->mOldCfg = {mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR}; // Also includes the changes during drawing (animation, scene reset, ...), which set their update flags themselves
198+
if (startupScript) {
199+
mStartupScriptDone = true;
200+
mConsole->runScriptFile(mDisplay->cfg().runScript); // Settings it changes are applied with the next frame
201+
}
197202
return retVal;
198203
}
199204

@@ -379,7 +384,7 @@ void GPUDisplayFrontend::StopDisplay()
379384
}
380385
std::lock_guard lock(mExitStateMutex);
381386
if (mDisplay->mInitResult == 1) { // For a new StartDisplay, not after a failed initialization or an error (undefined state)
382-
mStartDisplayDone = false;
387+
mStartDisplayDone = mStartupScriptDone = false;
383388
mDisplay->mInitResult = 0;
384389
}
385390
}

‎GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay
109109
std::condition_variable mInitCV; // With mExitStateMutex: InitDisplay (frontend thread) and StartDisplay (main thread) wait for each other at their end
110110
std::thread mFrontendThread; // Started by StartDisplay, joined by StopDisplay
111111
bool mStartDisplayDone = false; // Protected by mExitStateMutex
112+
bool mStartupScriptDone = false; // GPUSettingsDisplay::runScript runs after the first (suspended) frame
112113
std::array<uint8_t, KEY_MAP_MAX> mKeyDownMap = {};
113114

114115
// Keyboard / Mouse actions

‎GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -503,16 +503,27 @@ void GPUDisplayFrontend::listConfig()
503503
GPUInfo(" %2u %-28s %s", i, rtti.keyMapNames[i].c_str(), mappedKeys[i].c_str());
504504
}
505505
GPUInfo("");
506-
std::string available;
506+
static constexpr std::string_view availablePrefix = "Available keys: ";
507+
static constexpr size_t MAX_LINE_LENGTH = 240;
508+
std::string available(availablePrefix);
507509
for (uint16_t k : availableKeys) {
508-
available += (available.empty() ? "[" : " [") + rtti.getKeyName(k) + "]";
510+
const std::string key = "[" + rtti.getKeyName(k) + "]";
511+
if (available.size() > availablePrefix.size() && available.size() + 1 + key.size() > MAX_LINE_LENGTH) { // Wrap, without splitting a key
512+
GPUInfo("%s", available.c_str());
513+
available.assign(availablePrefix.size(), ' ');
514+
} else if (available.size() > availablePrefix.size()) {
515+
available += ' ';
516+
}
517+
available += key;
509518
}
510-
GPUInfo("Available keys: %s", available.c_str());
519+
GPUInfo("%s", available.c_str());
511520
GPUInfo("Help text:");
512521
for (const auto& line : rtti.helpText) {
513522
GPUInfo(" %s", line.c_str());
514523
}
515524
GPUInfo("");
525+
mConsole->printCommands([](const std::string& line) { GPUInfo("%s", line.c_str()); });
526+
GPUInfo("");
516527
GPUInfo("%zu options with RTTI, %u of them with a key, %u in the Qt GUI, %zu bound keys, %zu available keys", rtti.rttiEntries.size(), nOptionsWithKey, nOptionsInGUI, boundKeys.size(), availableKeys.size());
517528
}
518529

‎GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc‎

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,16 @@
1717

1818
void GPUDisplayFrontend::buildConsoleCommands()
1919
{
20+
using args = std::span<const GPUDisplayConsole::consoleArg>;
2021
auto& commands = mConsole->mConsoleCommands;
21-
commands["help"] = [this](std::span<const GPUDisplayConsole::consoleArg> args) {
22-
if (args.size()) {
23-
mConsole->consolePrint("help: no arguments expected");
24-
return;
25-
}
26-
std::vector<std::string> names;
27-
for (const auto& [name, function] : mConsole->mConsoleCommands) {
28-
names.emplace_back(name);
29-
}
30-
std::sort(names.begin(), names.end());
31-
mConsole->consolePrint("Console commands:");
32-
for (const auto& name : names) {
33-
mConsole->consolePrint(" %s", name.c_str());
34-
}
35-
};
36-
commands["clearHistory"] = [this](std::span<const GPUDisplayConsole::consoleArg> args) {
37-
if (args.size()) {
38-
mConsole->consolePrint("clearHistory: no arguments expected");
39-
return;
40-
}
41-
mConsole->mConsoleHistory.resize(1); // Keep the title
42-
mConsole->mConsoleTextCurrent = 0;
43-
mConsole->mConsoleInfoLastMessage.clear();
44-
};
22+
commands["help"] = {[this](args) { mConsole->printCommands([this](const std::string& line) { mConsole->consolePrint("%s", line.c_str()); }); }, "", "List available console commands"};
23+
commands["clearHistory"] = {[this](args) {
24+
mConsole->mConsoleHistory.resize(1); // Keep the title
25+
mConsole->mConsoleTextCurrent = 0;
26+
mConsole->mConsoleInfoLastMessage.clear();
27+
},
28+
"", "Clear the console history"};
29+
commands["runScript"] = {[this](args a) { mConsole->runScriptFile(std::get<std::string>(a[0])); }, "s", "Execute script file"};
4530
}
4631

4732
#endif // GPUDISPLAYFRONTENDCONFIGCONSOLE_INC

0 commit comments

Comments
 (0)