wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
SimulationHandler.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include <QFile>
7#include <QJsonArray>
8#include <QTextStream>
9
12#include "App/Scene/Scene.h"
14#include "App/UI/MainWindow.h"
15
17 : BaseHandler(mainWindow, validator)
18 , m_persistentDolphin(nullptr)
19{
20}
21
23{
24 // SimulationHandler is destroyed synchronously (not necessarily from within a
25 // running event loop), so deleteLater() may never fire — delete directly.
26 delete m_persistentDolphin;
27 m_persistentDolphin = nullptr;
28}
29
30QJsonObject SimulationHandler::handleCommand(const QString &command, const QJsonObject &params, const QJsonValue &requestId)
31{
32 if (command == "simulation_control") {
33 return handleSimulationControl(params, requestId);
34 } else if (command == "create_waveform") {
35 return handleCreateWaveform(params, requestId);
36 } else if (command == "export_waveform") {
37 return handleExportWaveform(params, requestId);
38 } else {
39 return createErrorResponse(QString("Unknown simulation command: %1").arg(command),
41 }
42}
43
44QJsonObject SimulationHandler::handleSimulationControl(const QJsonObject &params, const QJsonValue &requestId)
45{
46 if (!validateParameters(params, {"action"})) {
47 return createErrorResponse("Missing required parameter: action", requestId, JsonRpcError::InvalidParams);
48 }
49
50 QString errorMsg;
51 if (!validateNonEmptyString(params.value("action"), "action", errorMsg)) {
52 return createErrorResponse(errorMsg, requestId, JsonRpcError::InvalidParams);
53 }
54
55 QString action = params.value("action").toString();
56
57 Scene *scene = currentScene();
58 if (!scene) {
59 return createErrorResponse("No active circuit scene available", requestId, JsonRpcError::SceneNotAvailable);
60 }
61
62 Simulation *simulation = scene->simulation();
63 // Unreachable: Scene::simulation() returns the address of a value member, never null.
64 if (!simulation) {
65 return createErrorResponse("No simulation available", requestId, JsonRpcError::SimulationError); // LCOV_EXCL_LINE
66 }
67
68 return tryCommand([&]() -> QJsonObject { // LCOV_EXCL_LINE -- pattern 45: gcov misattributes this multi-line lambda-taking call's entry; the lambda body below is genuinely covered
69 if (action == "start") {
70 simulation->start();
71 } else if (action == "stop") {
72 simulation->stop();
73 } else if (action == "restart") {
74 simulation->restart();
75 } else if (action == "update") {
76 simulation->update();
77 } else {
78 return createErrorResponse(QString("Invalid action: %1").arg(action),
80 }
81 return createSuccessResponse(QJsonObject(), requestId);
82 }, "control simulation", requestId);
83}
84
85QJsonObject SimulationHandler::handleCreateWaveform(const QJsonObject &params, const QJsonValue &requestId)
86{
87 Scene *scene = currentScene();
88 if (!scene) {
89 return createErrorResponse("No active circuit scene available", requestId, JsonRpcError::SceneNotAvailable);
90 }
91
92 int duration = params.value("duration").toInt(32);
93 QJsonObject inputPatterns = params.value("input_patterns").toObject();
94
95 if (duration <= 0 || duration > SignalModel::kMaxColumns) {
96 return createErrorResponse(QString("Duration must be between 1 and %1").arg(SignalModel::kMaxColumns), requestId, JsonRpcError::ValidationError);
97 }
98
99 return tryCommand([&]() -> QJsonObject { // LCOV_EXCL_LINE -- pattern 45: gcov misattributes this multi-line lambda-taking call's entry; the lambda body below is genuinely covered
100 if (m_persistentDolphin) {
101 m_persistentDolphin->deleteLater();
102 m_persistentDolphin = nullptr;
103 }
104 m_persistentDolphin = new BewavedDolphin(scene, false, m_mainWindow, m_mainWindow);
105 BewavedDolphin *bewavedDolphin = m_persistentDolphin;
106
107 bewavedDolphin->prepare("");
108
109 bewavedDolphin->setLength(duration, false);
110
111 if (!inputPatterns.isEmpty()) {
112 for (auto it = inputPatterns.begin(); it != inputPatterns.end(); ++it) {
113 QString inputLabel = it.key();
114 QJsonArray pattern = it.value().toArray();
115
116 const int rowIndex = bewavedDolphin->inputRow(inputLabel);
117
118 if (rowIndex == -1) {
119 return createErrorResponse(QString("Input element with label '%1' not found").arg(inputLabel),
121 }
122
123 if (pattern.size() != duration) {
124 return createErrorResponse(QString("Pattern length for '%1' (%2) doesn't match duration (%3)") // LCOV_EXCL_LINE -- pattern 8: gcov misattributes this multi-line chained-.arg() call's first line even though it's genuinely reached
125 .arg(inputLabel).arg(pattern.size()).arg(duration),
127 }
128
129 for (int col = 0; col < duration; ++col) {
130 int value = pattern[col].toInt();
131 if (value != 0 && value != 1) {
132 return createErrorResponse(QString("Invalid pattern value %1 for '%2' at step %3 (must be 0 or 1)") // LCOV_EXCL_LINE -- pattern 8: gcov misattributes this multi-line chained-.arg() call's first line even though it's genuinely reached
133 .arg(value).arg(inputLabel).arg(col),
135 }
136 bewavedDolphin->setCellValue(rowIndex, col, value);
137 }
138 }
139 }
140
141 bewavedDolphin->run();
142
143 QJsonObject waveformData;
144 QJsonArray inputData;
145 QJsonArray outputData;
146
147 const auto waveform = bewavedDolphin->snapshot(duration);
148
149 for (const auto &signal : waveform.inputs) {
150 QJsonObject inputSignal;
151 inputSignal["label"] = signal.label;
152 inputSignal["type"] = "input";
153
154 QJsonArray values;
155 for (const int value : signal.values) {
156 values.append(value);
157 }
158 inputSignal["values"] = values;
159 inputData.append(inputSignal);
160 }
161
162 for (const auto &signal : waveform.outputs) {
163 QJsonObject outputSignal;
164 outputSignal["label"] = signal.label;
165 outputSignal["type"] = "output";
166
167 QJsonArray values;
168 for (const int value : signal.values) {
169 values.append(value);
170 }
171 outputSignal["values"] = values;
172 outputData.append(outputSignal);
173 }
174
175 waveformData["inputs"] = inputData;
176 waveformData["outputs"] = outputData;
177 waveformData["duration"] = duration;
178
179 QJsonObject result;
180 result["actual_duration"] = duration;
181 result["requested_duration"] = duration;
182 result["waveform_data"] = waveformData;
183 result["message"] = "Waveform created and analyzed successfully";
184 result["status"] = "ready";
185
186 return createSuccessResponse(result, requestId);
187 }, "create waveform", requestId);
188}
189
190QJsonObject SimulationHandler::handleExportWaveform(const QJsonObject &params, const QJsonValue &requestId)
191{
192 if (!validateParameters(params, {"filename", "format"})) {
193 return createErrorResponse("Missing required parameters: filename, format", requestId, JsonRpcError::InvalidParams);
194 }
195
196 QString filename = params.value("filename").toString();
197 QString format = params.value("format").toString().toLower();
198
199 if (format != "txt" && format != "png") {
200 return createErrorResponse("Only 'txt' and 'png' formats are supported for waveform export",
202 }
203
204 if (!m_persistentDolphin) {
205 return createErrorResponse("No waveform data available. Call create_waveform first.",
207 }
208
209 return tryCommand([&]() -> QJsonObject { // LCOV_EXCL_LINE -- pattern 45: gcov misattributes this multi-line lambda-taking call's entry; the lambda body below is genuinely covered
210 QJsonObject result;
211 result["filename"] = filename;
212 result["format"] = format;
213
214 if (format == "txt") {
215 QFile file(filename);
216 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
217 return createErrorResponse(QString("Failed to create file: %1").arg(filename),
218 requestId, JsonRpcError::FileError);
219 }
220
221 QTextStream stream(&file);
222 m_persistentDolphin->saveToTxt(stream);
223 stream.flush();
224 if (stream.status() != QTextStream::Ok || file.error() != QFileDevice::NoError) {
225 return createErrorResponse(QString("Failed to write waveform text: %1").arg(file.errorString()),
226 requestId, JsonRpcError::FileError);
227 }
228
229 } else if (format == "png") {
230 if (!m_persistentDolphin->exportToPng(filename)) {
231 return createErrorResponse("Failed to export waveform as PNG", requestId, JsonRpcError::FileError);
232 }
233 }
234
235 result["exported"] = true;
236 return createSuccessResponse(result, requestId);
237 }, "export waveform", requestId);
238}
BewavedDolphin waveform editor: digital signal creation, display, and export.
Main application window providing menus, toolbars, and tab management.
Main circuit editing scene with undo/redo and user interaction.
SignalModel for the beWavedDolphin waveform table.
Synchronous cycle-based simulation engine with event-driven clock support.
QJsonObject createSuccessResponse(const QJsonObject &result={}, const QJsonValue &requestId=QJsonValue()) const
QJsonObject tryCommand(Fn &&fn, const QString &action, const QJsonValue &requestId=QJsonValue())
Wraps fn in a try/catch, returning an error response on exception.
Definition BaseHandler.h:55
BaseHandler(MainWindow *mainWindow, const MCPValidator *validator)
QJsonObject createErrorResponse(const QString &error, const QJsonValue &requestId=QJsonValue(), int code=JsonRpcError::InternalError) const
MainWindow * m_mainWindow
Definition BaseHandler.h:85
bool validateParameters(const QJsonObject &params, const QStringList &required) const
bool validateNonEmptyString(const QJsonValue &value, const QString &paramName, QString &errorMsg) const
Scene * currentScene()
int inputRow(const QString &label) const
Returns the input row index whose element label equals label, or -1 (MCP access).
void run()
Runs the simulation for all input combinations and fills output rows.
void setCellValue(const int row, const int col, const int value)
Sets a single cell value in the waveform table.
void prepare(const QString &fileName={})
Prepares the waveform from fileName (or blank if empty).
WaveformSnapshot snapshot(int duration) const
Returns the input/output signals over the first duration columns (MCP access).
void setLength(const int simLength, const bool runSimulation=false)
Sets the number of time-step columns.
JSON Schema validator for MCP commands and responses using native json-schema-validator.
The top-level application window hosting the tab bar, menus, element palette, and editor.
Definition MainWindow.h:46
Simulation * simulation()
Returns the simulation engine associated with this scene.
Definition Scene.cpp:288
static constexpr int kMaxColumns
Definition SignalModel.h:37
QJsonObject handleCommand(const QString &command, const QJsonObject &params, const QJsonValue &requestId) override
SimulationHandler(MainWindow *mainWindow, const MCPValidator *validator)
void update()
Executes one simulation step (used by tests to advance the simulation manually).
void restart()
void start()
Starts the 1 ms simulation timer.
void stop()
Stops the simulation timer.
constexpr int SceneNotAvailable
No active circuit scene to operate on.
constexpr int SimulationError
Simulation control / waveform failure.
constexpr int MethodNotFound
The requested method does not exist or is unavailable.
constexpr int ElementNotFound
Referenced element id does not exist in the scene.
constexpr int FileError
File save/load/path error.
constexpr int ValidationError
Semantic validation failure (e.g. port index out of range, enum value not allowed).
constexpr int InvalidParams
Invalid method parameters (missing required, wrong type, etc.).