pure-cpp 1.0.0
A C++ physics simulation benchmark comparing performance with Python implementations
app_manager.cpp
Go to the documentation of this file.
1/**
2 * \file app_manager.cpp
3 * \brief Implementation of the application lifecycle manager.
4 * \author Le Bars, Yoann
5 *
6 * This file is part of the pure C++ benchmark.
7 */
8
9#include "app_manager.hpp"
10
11#include <QAction>
12#include <QMainWindow>
13#include <QMenu>
14#include <QMenuBar>
15#include <QWidget>
16#include <chrono>
17
18#include "app_profiler.hpp"
19
20namespace App {
21
22 /**
23 * \brief Constructs the application manager and sets application-wide
24 * metadata.
25 */
26 AppManager::AppManager() : mainWindow_(nullptr) {
27 QApplication::setApplicationName(Configuration::PROJECT_NAME);
28 QApplication::setOrganizationName("eiG");
29 const QString version = QStringLiteral("%1.%2.%3")
30 .arg(Configuration::VERSION_MAJOR)
31 .arg(Configuration::VERSION_MINOR)
32 .arg(Configuration::PATCH_VERSION);
33 QApplication::setApplicationVersion(version);
34 }
35
36 /**
37 * \brief Sets up all application components before running.
38 */
40 /* Pre-scan for the diagnostics flag. This is done before full argument
41 parsing to enable debug output during translation loading, which
42 happens before the logger is fully configured. */
43 bool diagnosticsEnabled = false;
44 for (const auto& arg : QApplication::arguments()) {
45 if (arg == QLatin1String("-D") ||
46 arg == QLatin1String("--diagnostics") ||
47 arg == QLatin1String("-c") ||
48 arg == QLatin1String("--debugConsole")) {
49 diagnosticsEnabled = true;
50 break;
51 }
52 }
54
55 // Create a lambda for translation to pass to the command-line parser.
56 // Use "CmdLine::CmdLineArgs" context to match the existing translations
57 // in the .ts files.
58 auto tr_func = [](const char* text) {
59 return QCoreApplication::translate("CmdLine::CmdLineArgs", text);
60 };
61 args_ = CmdLine::CmdLineArgs::parse(QApplication::arguments(), tr_func);
62 if (!args_) {
63 return false; // --help or --version was requested.
64 }
65
66 logger_ = std::make_unique<AppUtils::Logger>(*args_);
67 return true;
68 }
69
70 /**
71 * \brief Runs the main application event loop.
72 */
74 // Create the QML bridge (used for Display and actions)
76 bridge_ = std::make_unique<Window::QmlBridge>(
77 args_->toSimulationConfig(), this);
79
80 // Create a main window widget to host the Display
81 mainWindow_ = new QMainWindow();
82 mainWindow_->setWindowTitle(
83 QApplication::translate("MainWindow", "Pure C++"));
84 mainWindow_->resize(800, 600);
85
86 // Create native menu bar with QML bridge integration
87 // Use "MainWindow" context to match the translations from main.ui
88 QMenuBar* menuBar = mainWindow_->menuBar();
89
90 // File menu
91 QMenu* fileMenu =
92 menuBar->addMenu(QApplication::translate("MainWindow", "&File"));
93 QAction* quitAction =
94 fileMenu->addAction(QApplication::translate("MainWindow", "&Quit"));
95 quitAction->setShortcut(QKeySequence::Quit);
96 connect(quitAction, &QAction::triggered, bridge_.get(),
98
99 // Help menu
100 QMenu* helpMenu =
101 menuBar->addMenu(QApplication::translate("MainWindow", "&Help"));
102 QAction* aboutAction = helpMenu->addAction(
103 QApplication::translate("MainWindow", "About Pure C++"));
104 connect(aboutAction, &QAction::triggered, bridge_.get(),
106 QAction* aboutQtAction = helpMenu->addAction(
107 QApplication::translate("MainWindow", "About Qt"));
108 connect(aboutQtAction, &QAction::triggered, bridge_.get(),
110
111 // Add the Display container as the central widget
112 // The container is owned by mainWindow, so it will be destroyed when
113 // mainWindow is destroyed
114 if (bridge_->displayContainer()) {
115 mainWindow_->setCentralWidget(bridge_->displayContainer());
116 }
117
118 // Connect the application's aboutToQuit signal to our cleanup slot.
119 // This ensures cleanup happens before the application exits
120 connect(qApp, &QApplication::aboutToQuit, this, &AppManager::cleanup);
121
122 // Connect simulation finished to close the window
124 mainWindow_, &QMainWindow::close, Qt::QueuedConnection);
125
126 // Prevent automatic deletion on close - let QApplication handle
127 // destruction
128 mainWindow_->setAttribute(Qt::WA_DeleteOnClose, false);
129
130 // Show the window
131 mainWindow_->show();
132
133 // Start the simulation
134 bridge_->startSimulation();
135
136 // Start timing the event loop
138 int exitCode = QApplication::exec();
139 // Stop timing the event loop (exec() has returned)
141 return exitCode;
142 }
143
144 /**
145 * \brief Performs cleanup after the event loop has finished.
146 *
147 * According to Qt best practices, we should let QApplication handle widget
148 * destruction automatically. However, QWindowContainer has a special
149 * requirement: the QWindow (Display) must still be valid when the container
150 * is destroyed. Therefore, we must destroy the container explicitly BEFORE
151 * QmlBridge (which owns Display) is destroyed.
152 */
154 using CleanupClock = std::chrono::high_resolution_clock;
155 auto phaseStart = CleanupClock::now();
156
157 // Stop event loop timing if it's still running (should already be
158 // stopped, but be safe)
161
162 // Stop the physics thread and cleanup the display (prints profiling
163 // report) This is the only cleanup we need to do manually, as the
164 // thread must be stopped before QApplication is destroyed
165 phaseStart = CleanupClock::now();
166 if (bridge_) {
167 bridge_->cleanup();
168 }
170 std::chrono::duration<double>(CleanupClock::now() - phaseStart)
171 .count());
172
173 // CRITICAL: Destroy the Display BEFORE the container to avoid
174 // segfaults. QWindowContainer tries to access the Display during its
175 // destruction. If Display is already destroyed, the container can't
176 // corrupt it. This is the opposite of what one might expect, but it's
177 // what works.
178 //
179 // Two cleanup modes are supported:
180 // - fast (default): deferred destruction, non-blocking cleanup path
181 // - strict: synchronous destruction, conservative behavior
182 if (mainWindow_ && bridge_ && bridge_->displayContainer()) {
183 const bool useFastCleanup =
184 !args_ || args_->cleanup_mode_ != "strict";
185 QWidget* container = bridge_->displayContainer();
186 const auto manualDestroyStart = CleanupClock::now();
187 // Remove from mainWindow first to break parent-child relationship
188 phaseStart = CleanupClock::now();
189 mainWindow_->setCentralWidget(nullptr);
191 std::chrono::duration<double>(CleanupClock::now() - phaseStart)
192 .count());
193 // Destroy Display BEFORE container (prevents container from
194 // corrupting Display).
195 phaseStart = CleanupClock::now();
196 if (useFastCleanup) {
197 // In fast mode, container deletion is chained to Display
198 // destruction to preserve a deterministic safe order.
199 bridge_->releaseDisplayDeferred(container);
200 } else {
201 bridge_->releaseDisplay();
202 }
204 std::chrono::duration<double>(CleanupClock::now() - phaseStart)
205 .count());
206 // Fast mode schedules deletion in the event loop (non-blocking).
207 // Strict mode performs immediate deletion (synchronous).
208 phaseStart = CleanupClock::now();
209 if (useFastCleanup) {
210 // No direct deleteLater() here: releaseDisplayDeferred() links
211 // container deletion to Display::destroyed.
212 } else {
213 delete container;
214 }
216 std::chrono::duration<double>(CleanupClock::now() - phaseStart)
217 .count());
219 std::chrono::duration<double>(CleanupClock::now() -
220 manualDestroyStart)
221 .count());
222
223 // In strict mode we process pending events synchronously.
224 if (!useFastCleanup) {
225 phaseStart = CleanupClock::now();
226 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
228 std::chrono::duration<double>(CleanupClock::now() -
229 phaseStart)
230 .count());
231 }
232 }
233
234 // Let QApplication handle the rest of widget destruction automatically.
235 // mainWindow_ will be destroyed when QApplication is destroyed, but
236 // container is already gone, so no issue.
237
240 }
241
242} // namespace App
Global application profiling instrumentation.
int run()
Runs the main application event loop.
Definition: app_manager.cpp:73
bool setup()
Sets up all application components before running.
Definition: app_manager.cpp:39
QMainWindow * mainWindow_
The main window (stored for proper cleanup order).
Definition: app_manager.hpp:58
void cleanup()
Performs cleanup after the event loop has finished.
std::unique_ptr< AppUtils::Logger > logger_
The logger for the application.
Definition: app_manager.hpp:52
AppManager()
Constructs the application manager and sets application-wide metadata.
Definition: app_manager.cpp:26
std::unique_ptr< Window::QmlBridge > bridge_
The QML bridge for exposing C++ functionality.
Definition: app_manager.hpp:56
std::unique_ptr< CmdLine::CmdLineArgs > args_
The command line arguments for the application.
Definition: app_manager.hpp:54
static void addCleanupAppProcessEvents(double seconds)
Adds measured time spent in AppManager cleanup processEvents.
static void addCleanupAppManualDestroy(double seconds)
Adds measured time for manual destroy operations.
static void startEventLoop()
Starts timing the Qt event loop.
static void printReport()
Prints the profiling report.
static void addCleanupAppBridgeCleanup(double seconds)
Adds measured time spent in bridge/display cleanup call.
static void addCleanupAppDetachContainer(double seconds)
Adds measured time for detaching the display container.
static void stopEventLoop()
Stops timing the Qt event loop.
static void startCleanup()
Starts timing cleanup.
static void addCleanupAppContainerDeleteSchedule(double seconds)
Adds measured time for container delete action.
static void stopWindowCreation()
Stops timing 3D window creation.
static void addCleanupAppReleaseDisplay(double seconds)
Adds measured time for explicit display release.
static void startWindowCreation()
Starts timing 3D window creation.
static void stopCleanup()
Stops timing cleanup.
static void loadTranslations(bool in_diagnosticsEnabled)
Loads and installs the best-matching translation for the system’s locale.
void showAboutQt()
Slot to display the "About Qt" dialogue.
Definition: qml_bridge.cpp:125
void quit()
Slot to quit the application.
Definition: qml_bridge.cpp:129
void simulationFinished()
Emitted when the simulation finishes.
void showAbout()
Slot to display the "About" dialogue.
Definition: qml_bridge.cpp:119
static std::unique_ptr< CmdLineArgs > parse(const QStringList &q_args, const std::function< QString(const char *)> &tr_func)
Parses and validates command-line arguments.