wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
Simulation.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 <algorithm>
7
8#include <QGraphicsView>
9#include <QGuiApplication>
10#include <QScreen>
11
13#include "App/Core/Common.h"
14#include "App/Core/Priorities.h"
18#include "App/Element/IC.h"
20#include "App/Wiring/Port.h"
21
22using namespace std::chrono_literals;
23
25 : QObject(parent)
26 , m_host(host)
27{
28 // 1ms tick drives the simulation at ~1000 steps/second — fast enough for
29 // human perception while keeping CPU load predictable.
30 m_timer.setInterval(1ms);
31 connect(&m_timer, &QTimer::timeout, this, &Simulation::update);
32
33 // Derive the visual refresh interval from the monitor's refresh rate so
34 // we match the display without wasting repaints. Falls back to 60 Hz.
35 if (auto *screen = QGuiApplication::primaryScreen()) {
36 const qreal hz = screen->refreshRate();
37 if (hz > 0) {
38 m_visualTickInterval = (std::max)(1, static_cast<int>(1000.0 / hz));
39 }
40 }
41}
42
44{
45 m_visualThrottleEnabled = enabled;
46 if (enabled) {
47 m_visualTickCount = 0; // start fresh so throttle resumes cleanly
48 }
49}
50
52{
53 // Lazily build the simulation layer on the first tick after a restart so
54 // that circuit edits made while stopped are always reflected when the
55 // simulation resumes.
56 if (!m_initialized && !initialize()) {
57 return;
58 }
59
60 // Bug 5 invariant: the H2 cluster fix established that m_initialized=true
61 // implies the topology vectors reflect the current scene. Any future change
62 // that touches m_initialized without rebuilding the vectors trips here in
63 // debug/asan/ubsan builds — much earlier than a tick-time crash.
64 Q_ASSERT(m_initialized);
65
66 // Snapshot the topology vectors before iterating: if restart() is invoked
67 // reentrantly while this tick is mid-flight, it clears and rebuilds
68 // m_clocks/m_inputs/m_sequentialElements/m_sortedElements/m_outputs, which
69 // would invalidate any in-flight range-for iterator over the members
70 // themselves. Iterating local copies keeps this tick's iteration valid for
71 // its remainder even if the members mutate underneath.
72 const auto clocks = m_clocks;
73 const auto inputs = m_inputs;
74 const auto sequentialElements = m_sequentialElements;
75 const auto elements = m_sortedElements;
76 const auto outputs = m_outputs;
77
78 // Clock elements are the only truly time-driven components; all other logic
79 // is combinational and responds immediately to their values.
80 if (m_timer.isActive()) {
81 const auto globalTime = std::chrono::steady_clock::now();
82
83 for (auto *clock : clocks) {
84 if (clock) {
85 clock->updateClock(globalTime);
86 }
87 }
88 }
89
90 // Phase 1: propagate user-controlled inputs (switches, buttons, etc.)
91 for (auto *inputElm : inputs) {
92 if (inputElm) {
93 inputElm->updateOutputs();
94 }
95 }
96
97 // Advance the visual throttle on every tick (skipped or not) so the phase 3-4
98 // cadence stays time-based. Non-interactive callers (tests, BeWavedDolphin's
99 // throttle disabler) flush on every tick, as before.
100 const bool visualsDue = !(m_visualThrottleEnabled && Application::interactiveMode)
101 || (++m_visualTickCount >= m_visualTickInterval);
102 if (visualsDue) {
103 m_visualTickCount = 0;
104 }
105
106 // Skip provably-idle ticks. A completed sweep whose settle passes converged is a
107 // fixed point of the deterministic element functions; it can only be left by a
108 // clock flip, an input-element change (both flagged through setOutputValue()'s
109 // change detection -- user toggles included, they write the same way), or a
110 // structural edit (restart() clears m_atFixedPoint). Everything else recomputes
111 // bit-identical outputs 1000x/s, which on large clocked circuits is almost every
112 // tick. The flags are cleared as they are read so one flip triggers one sweep.
113 bool sourceChanged = false;
114 for (auto *clock : clocks) {
115 if (clock && clock->outputChanged()) {
116 sourceChanged = true;
117 clock->clearOutputChanged();
118 }
119 }
120 for (auto *inputElm : inputs) {
121 if (inputElm && inputElm->outputChanged()) {
122 sourceChanged = true;
123 inputElm->clearOutputChanged();
124 }
125 }
126
127 if (!sourceChanged && m_atFixedPoint) {
128 if (visualsDue && m_visualsDirty) {
129 pushVisualStatuses(elements, outputs);
130 m_visualsDirty = false;
131 }
132 return;
133 }
134
135 // Phase 2: update all GraphicElements in topological order.
136 //
137 // Synchronous sequential elements (flip-flops, latches — ElementGroup::Memory,
138 // collected across the whole IC hierarchy) get non-blocking semantics: their
139 // outputs are staged during the pass and committed together afterwards, so any
140 // combinational logic between them (gated clocks especially) reads the
141 // pre-tick state. This matches real synchronous hardware and the exported
142 // SystemVerilog's non-blocking (<=) model. Gate-built feedback latches are
143 // ElementGroup::Gate, not Memory, so they are never deferred and settle
144 // through the existing path unchanged.
145 for (auto *element : sequentialElements) {
146 if (element) {
147 element->beginDeferredCommit();
148 }
149 }
150
151 // A plain topological sweep reaches its fixed point in one pass by construction;
152 // only feedback settling can fail to converge (oscillating circuits).
153 bool sweepConverged = true;
154
155 if (m_simHasFeedbackElements) {
156 // Use iterative settling for circuits with feedback loops.
157 sweepConverged = updateWithIterativeSettling(elements);
158 } else {
159 // Phase 2: update all logic elements in topologically sorted order so
160 // every gate sees its inputs before computing its output.
161 for (auto *element : elements) {
162 if (element) {
163 element->updateLogic();
164 }
165 }
166 }
167
168 // Publish every staged sequential output simultaneously (global commit).
169 bool anySequentialChanged = false;
170 for (auto *element : sequentialElements) {
171 if (element) {
172 element->clearOutputChanged();
173 element->commitDeferredOutputs();
174 if (element->outputChanged()) {
175 anySequentialChanged = true;
176 }
177 }
178 }
179
180 // Post-edge settle (the second half of a clock cycle / a bounded delta
181 // cycle): once flip-flops have committed, re-propagate their new outputs
182 // through combinational logic and IC output boundaries so the end-of-tick
183 // state is a true fixed point — and so asynchronous overrides (preset/clear)
184 // surface immediately rather than lagging a tick behind. Sequential elements
185 // are skipped here so they are not re-clocked. Only needed on ticks where an
186 // edge or async override actually changed sequential state; steady-state
187 // ticks pay nothing. Feed-forward logic settles in one topological sweep;
188 // iterate only when combinational feedback is present.
189 if (anySequentialChanged) {
190 const int maxPasses = m_simHasFeedbackElements ? kMaxSettleIterations : 1;
191 for (int pass = 0; pass < maxPasses; ++pass) {
192 bool changed = false;
193 for (auto *element : elements) {
194 if (element && element->elementGroup() != ElementGroup::Memory) {
195 element->clearOutputChanged();
196 element->resettleCombinational();
197 changed = changed || element->outputChanged();
198 }
199 }
200 if (!changed) {
201 break;
202 }
203 if (pass == maxPasses - 1) {
204 // Exhausted the budget while still changing: not a fixed point.
205 sweepConverged = false;
206 }
207 }
208 }
209
210 // This sweep's result is a fixed point unless a settle pass ran out of budget while
211 // still changing (an oscillating feedback circuit, which must keep sweeping).
212 m_atFixedPoint = sweepConverged;
213 m_visualsDirty = true;
214
215 // Visual updates only need to run at display-refresh rate, not at simulation
216 // rate (1000 Hz) — skipping most ticks avoids dirtying QGraphicsItems that would
217 // be overwritten before the next repaint. In non-interactive (test) mode every
218 // tick flushes so tests see immediate visual state after each step.
219 if (visualsDue) {
220 pushVisualStatuses(elements, outputs);
221 m_visualsDirty = false;
222 }
223}
224
225void Simulation::pushVisualStatuses(const QVector<GraphicElement *> &elements, const QVector<GraphicElement *> &outputs)
226{
227 // Phase 3: push computed logic values onto all output port visuals.
228 // Iterating elements (not connections) ensures unconnected output ports
229 // (e.g. -Q of a flip-flop with no wire attached) are also updated.
230 // setStatus() fans out through any attached connections automatically.
231 for (auto *element : elements) {
232 if (element) {
233 for (auto *outputPort : element->outputs()) {
234 updatePort(outputPort);
235 }
236 }
237 }
238
239 // Phase 4: refresh output element visuals (LEDs, buzzers, etc.) using their input ports
240 for (auto *outputElm : outputs) {
241 if (outputElm) {
242 for (auto *inputPort : outputElm->inputs()) {
243 if (inputPort) {
244 updatePort(inputPort);
245 }
246 }
247 }
248 }
249}
250
251void Simulation::updatePort(OutputPort *port)
252{
253 if (!port) {
254 return;
255 }
256
257 auto *element = port->graphicElement();
258 if (!element) {
259 port->setStatus(Status::Unknown);
260 return;
261 }
262
263 port->setStatus(element->outputValue(port->index()));
264}
265
266void Simulation::updatePort(InputPort *port)
267{
268 if (!port) {
269 return;
270 }
271
272 const auto &conns = port->connections();
273 const Status status = (!conns.isEmpty() && conns.first()->startPort())
274 ? conns.first()->startPort()->status()
275 : port->defaultValue();
276 port->setStatus(status);
277
278 // Output elements (LEDs, buzzers) need an explicit repaint to show the new state.
279 auto *elm = port->graphicElement();
280 if (elm && elm->elementGroup() == ElementGroup::Output) {
281 elm->refresh();
282 }
283}
284
286{
287 // Invalidate the cached topology. Clearing the flag alone is not
288 // enough: update() iterates m_sortedElements/m_clocks/m_inputs/m_outputs
289 // before a re-initialize can run (for instance when Application::notify()
290 // spins a QMessageBox nested event loop), and any entry that refers to an
291 // element we've already freed faults on its vtable read. Drop every
292 // reference so the next tick's initialize() can rebuild them cleanly.
293 m_initialized = false;
294 // A structural edit invalidates the fixed point: the next tick must sweep, and its
295 // visuals must flush even if the throttle boundary lands on a later skipped tick.
296 m_atFixedPoint = false;
297 m_visualsDirty = true;
298 m_sortedElements.clear();
299 m_sequentialElements.clear();
300 m_clocks.clear();
301 m_inputs.clear();
302 m_outputs.clear();
303 // Bug 4 postcondition: any future cached state added to Simulation must be
304 // cleared above. This assert documents the invariant for future maintainers
305 // and trips immediately if a new vector is forgotten.
306 Q_ASSERT(!m_initialized);
307 Q_ASSERT(m_sortedElements.isEmpty() && m_sequentialElements.isEmpty()
308 && m_clocks.isEmpty() && m_inputs.isEmpty() && m_outputs.isEmpty());
309}
310
312{
313 return m_timer.isActive();
314}
315
317{
318 return m_simFeedbackNodes.contains(element);
319}
320
322{
323 m_timer.stop();
324 if (m_host) {
325 m_host->setMuted(true);
326 }
327}
328
330{
331 qCDebug(zero) << "Starting simulation.";
332
333 if (!m_initialized) {
334 initialize();
335 } else {
336 // After a pause the wall clock has advanced, so clocks must be reset
337 // to "now" — otherwise they would fire many missed ticks immediately.
338 const auto globalTime = std::chrono::steady_clock::now();
339 for (auto *clock : std::as_const(m_clocks)) {
340 if (clock) {
341 clock->resetClock(globalTime);
342 }
343 }
344 }
345
346 m_timer.start();
347 if (m_host) {
348 m_host->setMuted(m_userMuted);
349 }
350 qCDebug(zero) << "Simulation started.";
351}
352
353void Simulation::setUserMuted(const bool muted)
354{
355 m_userMuted = muted;
356 if (m_host) {
357 m_host->setMuted(muted);
358 }
359}
360
362{
363 return m_userMuted;
364}
365
366bool Simulation::updateWithIterativeSettling(const QVector<GraphicElement *> &elements)
367{
368 const bool converged = iterativeSettle(elements);
369 if (!converged && !m_convergenceWarned) {
370 m_convergenceWarned = true;
371 qDebug() << "Feedback circuit did not converge after 10 iterations";
372 emit simulationWarning(tr("Warning: feedback circuit did not converge — the circuit may be oscillating."));
373 }
374 return converged;
375}
376
378{
379 if (!m_host) {
380 return false;
381 }
382
383 // Rebuild all categorised lists from scratch so stale pointers from
384 // a previous circuit state don't linger after undo/redo or file load.
385 m_convergenceWarned = false;
386 m_clocks.clear();
387 m_outputs.clear();
388 m_inputs.clear();
389 m_sortedElements.clear();
390 m_sequentialElements.clear();
391
392 QVector<GraphicElement *> elements;
393 auto items = m_host->simulationItems();
394
395 // Sort items by position coordinates for consistent ordering between runs.
396 // QGraphicsScene::items() returns items in an unspecified Z/stacking order;
397 // stabilising on (Y, X) gives deterministic wire-update sequences across
398 // sessions and makes test results reproducible.
399 std::stable_sort(items.begin(), items.end(), [](const auto &a, const auto &b) {
400 if (!a || !b) {
401 return a != nullptr;
402 }
403 // Sort by Y coordinate first, then X coordinate for consistent 2D ordering
404 if (qFuzzyCompare(a->y(), b->y())) {
405 return a->x() < b->x();
406 }
407 return a->y() < b->y();
408 });
409
410 // A scene with only one item is the scene border/background rectangle;
411 // there is no circuit yet, so building a simulation graph would be pointless.
412 if (items.size() == 1) {
413 return false;
414 }
415
416 qCDebug(two) << "GENERATING SIMULATION LAYER.";
417
418 const auto globalTime = std::chrono::steady_clock::now();
419
420 for (auto *item : std::as_const(items)) {
421 if (!item) {
422 continue;
423 }
424
425 if (item->type() == GraphicElement::Type) {
426 auto *element = qgraphicsitem_cast<GraphicElement *>(item);
427 if (!element) {
428 continue;
429 }
430 elements.append(element);
431
432 if (element->elementType() == ElementType::Clock) {
433 auto *clock = qobject_cast<Clock *>(element);
434 if (clock) {
435 m_clocks.append(clock);
436 clock->resetClock(globalTime);
437 }
438 }
439
440 if (element->elementGroup() == ElementGroup::Input) {
441 auto *input = qobject_cast<GraphicElementInput *>(element);
442 if (input) {
443 m_inputs.append(input);
444 }
445 }
446
447 if (element->elementGroup() == ElementGroup::Output) {
448 m_outputs.append(element);
449 }
450 }
451 }
452
453 qCDebug(zero) << "Elements read: " << elements.size();
454
455 if (elements.empty()) {
456 return false;
457 }
458
459 // Initialize simulation vectors on all scene-level elements
460 for (auto *elm : std::as_const(elements)) {
461 elm->initSimulationVectors(elm->inputSize(), elm->outputSize());
462 }
463
464 // Build connection graph
465 buildConnectionGraph(elements);
466 connectWirelessElements(elements);
467
468 // Initialize IC internal simulation graphs
469 for (auto *elm : std::as_const(elements)) {
470 if (elm->elementType() == ElementType::IC) {
471 static_cast<IC *>(elm)->initializeSimulation();
472 }
473 }
474
475 // Topological sort with feedback detection
476 sortSimElements(elements);
477
478 // Collect every synchronous sequential element (across the IC hierarchy) so
479 // Phase 2 can give them non-blocking commit semantics.
480 collectSequentialElements(elements);
481
482 m_initialized = true;
483
484 qCDebug(zero) << "Finished simulation layer.";
485 return true;
486}
487
488// --- Simulation graph building ---
489
490void Simulation::buildConnectionGraph(const QVector<GraphicElement *> &elements)
491{
492 for (auto *elm : std::as_const(elements)) {
493 for (int i = 0; i < elm->inputSize(); ++i) {
494 auto *inputPort = elm->inputPort(i);
495 const auto &connections = inputPort->connections();
496
497 if (connections.size() == 1) {
498 auto *connection = connections.constFirst();
499 if (!connection) {
500 continue;
501 }
502 if (auto *outputPort = connection->startPort()) {
503 auto *sourceElement = outputPort->graphicElement();
504 if (sourceElement) {
505 elm->connectPredecessor(i, sourceElement, outputPort->index());
506 }
507 }
508 }
509 }
510 }
511}
512
513void Simulation::connectWirelessElements(const QVector<GraphicElement *> &elements)
514{
515 const auto txMap = buildTxMap(elements);
516
517 // Wire each Rx node's input to the matching Tx node's output.
518 // connectPredecessor() overwrites whatever buildConnectionGraph() set,
519 // so the topological sort will see the true wireless dependency.
520 for (auto *elm : std::as_const(elements)) {
521 if (elm->wirelessMode() != WirelessMode::Rx || elm->label().isEmpty()) {
522 continue;
523 }
524 if (auto *txElement = txMap.value(elm->label(), nullptr)) {
525 elm->connectPredecessor(0, txElement, 0);
526 }
527 }
528}
529
530QHash<QString, GraphicElement *> Simulation::buildTxMap(const QVector<GraphicElement *> &elements)
531{
532 QHash<QString, GraphicElement *> txMap;
533 for (auto *elm : std::as_const(elements)) {
534 if (elm->wirelessMode() == WirelessMode::Tx && !elm->label().isEmpty()) {
535 if (!txMap.contains(elm->label())) {
536 txMap.insert(elm->label(), elm);
537 }
538 }
539 }
540 return txMap;
541}
542
543QHash<GraphicElement *, QVector<GraphicElement *>> Simulation::buildSuccessorGraph(
544 const QVector<GraphicElement *> &elements,
545 const QHash<QString, GraphicElement *> &txMap)
546{
547 QHash<GraphicElement *, QVector<GraphicElement *>> successors;
548
549 // Build successor edges from physical connections
550 for (auto *elm : std::as_const(elements)) {
551 for (auto *outputPort : elm->outputs()) {
552 for (auto *conn : outputPort->connections()) {
553 if (auto *endPort = conn->endPort()) {
554 auto *successor = endPort->graphicElement();
555 if (successor) {
556 auto &vec = successors[elm];
557 if (!vec.contains(successor)) {
558 vec.append(successor);
559 }
560 }
561 }
562 }
563 }
564 }
565
566 // Add wireless Tx→Rx edges.
567 // connectWirelessElements() already set predecessors for simulation input routing,
568 // but those don't create Connection objects, so the connection-walking loop above
569 // doesn't see wireless dependencies. We must add them explicitly here for correct
570 // topological ordering.
571 for (auto *elm : std::as_const(elements)) {
572 if (elm->wirelessMode() == WirelessMode::Rx && !elm->label().isEmpty()) {
573 if (auto *tx = txMap.value(elm->label(), nullptr)) {
574 auto &txVec = successors[tx];
575 if (!txVec.contains(elm)) {
576 txVec.append(elm);
577 }
578 }
579 }
580 }
581
582 return successors;
583}
584
586 const QVector<GraphicElement *> &elements,
587 const QHash<GraphicElement *, QVector<GraphicElement *>> &successors)
588{
589 SortResult result;
590
591 QVector<GraphicElement *> rawPtrs(elements);
592 calculatePriorities(rawPtrs, successors, result.priorities);
593 result.feedbackNodes = findFeedbackNodes(rawPtrs, successors);
594
595 result.sorted = elements;
596 std::stable_sort(result.sorted.begin(), result.sorted.end(),
597 [&result](const auto *a, const auto *b) {
598 return result.priorities.value(const_cast<GraphicElement *>(a), -1)
599 > result.priorities.value(const_cast<GraphicElement *>(b), -1);
600 });
601
602 return result;
603}
604
605bool Simulation::iterativeSettle(const QVector<GraphicElement *> &elements, const int maxIterations)
606{
607 for (int iteration = 0; iteration < maxIterations; ++iteration) {
608 for (auto *element : std::as_const(elements)) {
609 if (!element) {
610 continue;
611 }
612 element->clearOutputChanged();
613 element->updateLogic();
614 }
615
616 const bool converged = std::none_of(elements.cbegin(), elements.cend(),
617 [](const auto *element) { return element && element->outputChanged(); });
618
619 if (converged) {
620 return true;
621 }
622 }
623 return false;
624}
625
626void Simulation::sortSimElements(const QVector<GraphicElement *> &elements)
627{
628 const auto txMap = buildTxMap(elements);
629 const auto successors = buildSuccessorGraph(elements, txMap);
630 const auto result = topologicalSort(elements, successors);
631
632 m_simPriorities.clear();
633 m_simFeedbackNodes.clear();
634 for (auto *elm : std::as_const(elements)) {
635 m_simPriorities[elm] = result.priorities.value(elm, -1);
636 if (result.feedbackNodes.contains(elm)) {
637 m_simFeedbackNodes.insert(elm);
638 }
639 }
640 m_simHasFeedbackElements = !m_simFeedbackNodes.isEmpty();
641 m_sortedElements = result.sorted;
642}
643
644void Simulation::collectSequentialElements(const QVector<GraphicElement *> &elements)
645{
646 for (auto *elm : std::as_const(elements)) {
647 if (!elm) {
648 continue;
649 }
650 if (elm->elementGroup() == ElementGroup::Memory) {
651 m_sequentialElements.append(elm);
652 }
653 if (elm->elementType() == ElementType::IC) {
654 collectSequentialElements(static_cast<IC *>(elm)->internalElements());
655 }
656 }
657}
Custom QApplication subclass with exception handling and main-window access.
Graphic element for the real-time clock input.
Common logging utilities, the Pandaception error type, and helper macros.
#define qCDebug(category)
Definition Common.h:29
Connection: a wire that connects an output port to an input port in the circuit scene.
Enums::Status Status
Definition Enums.h:106
Abstract base class for all graphical circuit elements.
Integrated Circuit (IC) graphic element that encapsulates a sub-circuit file.
Port classes: Port (base), InputPort, and OutputPort.
Graph algorithms for topological priority assignment and cycle detection.
QSet< T * > findFeedbackNodes(const QVector< T * > &elements, const QHash< T *, QVector< T * > > &successors)
Finds all nodes that participate in feedback loops (cycles).
Definition Priorities.h:29
void calculatePriorities(const QVector< T * > &elements, const QHash< T *, QVector< T * > > &successors, QHash< T *, int > &outPriorities)
Priority calculation for directed graphs.
Definition Priorities.h:224
Interface exposing the narrow slice of Scene that Simulation depends on.
Synchronous cycle-based simulation engine with event-driven clock support.
static bool interactiveMode
Definition Application.h:73
Abstract base class for all graphical circuit elements in wiRedPanda.
Graphic element representing an Integrated Circuit (sub-circuit) box.
Definition IC.h:31
A port that receives a signal (the destination end of a wire).
Definition Port.h:229
void setStatus(const Status status) override
Definition Port.cpp:307
A port that drives a signal (the source end of a wire).
Definition Port.h:261
void setStatus(const Status status) override
Definition Port.cpp:361
GraphicElement * graphicElement()
Returns the graphic element that owns this port.
Definition Port.h:66
int index() const
Returns the port's visual/logical index within the element.
Definition Port.cpp:138
Status defaultValue() const
Returns the default status applied when the port is unconnected.
Definition Port.h:76
const QList< Connection * > & connections() const
Returns the list of wires attached to this port.
Definition Port.cpp:57
Narrow interface letting Simulation reach its host scene without naming the concrete Scene class.
void update()
Executes one simulation step (used by tests to advance the simulation manually).
static SortResult topologicalSort(const QVector< GraphicElement * > &elements, const QHash< GraphicElement *, QVector< GraphicElement * > > &successors)
Topologically sorts elements using the successor graph, detects feedback loops.
void setVisualThrottleEnabled(bool enabled)
Simulation(SimulationHost *host, QObject *parent=nullptr)
Constructs a Simulation bound to host.
static bool iterativeSettle(const QVector< GraphicElement * > &elements, int maxIterations=kMaxSettleIterations)
static QHash< QString, GraphicElement * > buildTxMap(const QVector< GraphicElement * > &elements)
Builds a label→element map for wireless Tx nodes. First Tx per label wins.
void setUserMuted(bool muted)
Sets whether the user has explicitly muted audio; persists across stop/start cycles.
void simulationWarning(const QString &message)
Emitted (at most once per initialize()) when a feedback circuit fails to converge.
void restart()
static void buildConnectionGraph(const QVector< GraphicElement * > &elements)
bool initialize()
Builds the simulation graph from the current scene elements.
static void connectWirelessElements(const QVector< GraphicElement * > &elements)
static QHash< GraphicElement *, QVector< GraphicElement * > > buildSuccessorGraph(const QVector< GraphicElement * > &elements, const QHash< QString, GraphicElement * > &txMap)
Builds a successor adjacency list from connection graph + wireless Tx→Rx edges.
static constexpr int kMaxSettleIterations
Definition Simulation.h:46
void start()
Starts the 1 ms simulation timer.
bool isRunning()
Returns true if the simulation timer is currently running.
bool isUserMuted() const
Returns true if the user has explicitly muted audio.
bool isInFeedbackLoop(const GraphicElement *element) const
Returns true if element is part of a combinational feedback loop.
void stop()
Stops the simulation timer.
Result of topological sort with feedback detection.
Definition Simulation.h:123
QSet< GraphicElement * > feedbackNodes
Elements in feedback loops.
Definition Simulation.h:126
QVector< GraphicElement * > sorted
Elements in priority order (highest first).
Definition Simulation.h:124
QHash< GraphicElement *, int > priorities
Priority per element.
Definition Simulation.h:125