8#include <QGraphicsView>
9#include <QGuiApplication>
22using namespace std::chrono_literals;
30 m_timer.setInterval(1ms);
38 connect(&m_timer, &QTimer::timeout,
this, [
this] {
44 if (
auto *screen = QGuiApplication::primaryScreen()) {
45 const qreal hz = screen->refreshRate();
47 m_visualTickInterval = (std::max)(1,
static_cast<int>(1000.0 / hz));
54 m_visualThrottleEnabled = enabled;
56 m_visualTickCount = 0;
73 Q_ASSERT(m_initialized);
81 const auto clocks = m_clocks;
82 const auto inputs = m_inputs;
83 const auto sequentialElements = m_sequentialElements;
84 const auto elements = m_sortedElements;
85 const auto outputs = m_outputs;
89 if (m_timer.isActive()) {
90 const auto globalTime = std::chrono::steady_clock::now();
92 for (
auto *clock : clocks) {
94 clock->updateClock(globalTime);
100 for (
auto *inputElm : inputs) {
102 inputElm->updateOutputs();
110 || (++m_visualTickCount >= m_visualTickInterval);
112 m_visualTickCount = 0;
122 bool sourceChanged =
false;
123 for (
auto *clock : clocks) {
124 if (clock && clock->outputChanged()) {
125 sourceChanged =
true;
126 clock->clearOutputChanged();
129 for (
auto *inputElm : inputs) {
130 if (inputElm && inputElm->outputChanged()) {
131 sourceChanged =
true;
132 inputElm->clearOutputChanged();
136 if (!sourceChanged && m_atFixedPoint) {
137 if (visualsDue && m_visualsDirty) {
138 pushVisualStatuses(elements, outputs);
139 m_visualsDirty =
false;
154 for (
auto *element : sequentialElements) {
156 element->beginDeferredCommit();
162 bool sweepConverged =
true;
164 if (m_simHasFeedbackElements) {
166 sweepConverged = updateWithIterativeSettling(elements);
170 for (
auto *element : elements) {
172 element->updateLogic();
178 bool anySequentialChanged =
false;
179 for (
auto *element : sequentialElements) {
181 element->clearOutputChanged();
182 element->commitDeferredOutputs();
183 if (element->outputChanged()) {
184 anySequentialChanged =
true;
198 if (anySequentialChanged) {
200 for (
int pass = 0; pass < maxPasses; ++pass) {
201 bool changed =
false;
202 for (
auto *element : elements) {
203 if (element && element->elementGroup() != ElementGroup::Memory) {
204 element->clearOutputChanged();
205 element->resettleCombinational();
206 changed = changed || element->outputChanged();
212 if (pass == maxPasses - 1) {
214 sweepConverged =
false;
221 m_atFixedPoint = sweepConverged;
222 m_visualsDirty =
true;
229 pushVisualStatuses(elements, outputs);
230 m_visualsDirty =
false;
234void Simulation::pushVisualStatuses(
const QVector<GraphicElement *> &elements,
const QVector<GraphicElement *> &outputs)
240 for (
auto *element : elements) {
242 for (
auto *outputPort : element->outputs()) {
243 updatePort(outputPort);
249 for (
auto *outputElm : outputs) {
251 for (
auto *inputPort : outputElm->inputs()) {
253 updatePort(inputPort);
275void Simulation::updatePort(
InputPort *port)
282 const Status status = (!conns.isEmpty() && conns.first()->startPort())
283 ? conns.first()->startPort()->status()
289 if (elm && elm->elementGroup() == ElementGroup::Output) {
302 m_initialized =
false;
305 m_atFixedPoint =
false;
306 m_visualsDirty =
true;
307 m_sortedElements.clear();
308 m_sequentialElements.clear();
315 Q_ASSERT(!m_initialized);
316 Q_ASSERT(m_sortedElements.isEmpty() && m_sequentialElements.isEmpty()
317 && m_clocks.isEmpty() && m_inputs.isEmpty() && m_outputs.isEmpty());
322 return m_timer.isActive();
327 return m_simFeedbackNodes.contains(element);
334 if (m_timer.isActive()) {
335 m_pausedAt = std::chrono::steady_clock::now();
336 m_hasPausedAt =
true;
340 m_host->setMuted(
true);
346 qCDebug(zero) <<
"Starting simulation.";
348 if (!m_initialized) {
350 }
else if (m_hasPausedAt) {
359 const auto pause = std::chrono::steady_clock::now() - m_pausedAt;
360 for (
auto *clock : std::as_const(m_clocks)) {
362 clock->shiftClock(pause);
365 m_hasPausedAt =
false;
370 const auto globalTime = std::chrono::steady_clock::now();
371 for (
auto *clock : std::as_const(m_clocks)) {
373 clock->resetClock(globalTime);
380 m_host->setMuted(m_userMuted);
382 qCDebug(zero) <<
"Simulation started.";
389 m_host->setMuted(muted);
398bool Simulation::updateWithIterativeSettling(
const QVector<GraphicElement *> &elements)
401 if (!converged && !m_convergenceWarned) {
402 m_convergenceWarned =
true;
403 qDebug() <<
"Feedback circuit did not converge after 10 iterations";
404 emit
simulationWarning(tr(
"Warning: feedback circuit did not converge — the circuit may be oscillating."));
417 m_convergenceWarned =
false;
421 m_sortedElements.clear();
422 m_sequentialElements.clear();
424 QVector<GraphicElement *> elements;
425 auto items = m_host->simulationItems();
431 std::stable_sort(items.begin(), items.end(), [](
const auto &a,
const auto &b) {
436 if (qFuzzyCompare(a->y(), b->y())) {
437 return a->x() < b->x();
439 return a->y() < b->y();
444 if (items.size() == 1) {
448 qCDebug(two) <<
"GENERATING SIMULATION LAYER.";
450 const auto globalTime = std::chrono::steady_clock::now();
452 for (
auto *item : std::as_const(items)) {
458 auto *element = qgraphicsitem_cast<GraphicElement *>(item);
462 elements.append(element);
464 if (element->elementType() == ElementType::Clock) {
465 auto *clock = qobject_cast<Clock *>(element);
467 m_clocks.append(clock);
468 clock->resetClock(globalTime);
472 if (element->elementGroup() == ElementGroup::Input) {
473 auto *input = qobject_cast<GraphicElementInput *>(element);
475 m_inputs.append(input);
479 if (element->elementGroup() == ElementGroup::Output) {
480 m_outputs.append(element);
485 qCDebug(zero) <<
"Elements read: " << elements.size();
489 m_hasPausedAt =
false;
491 if (elements.empty()) {
496 for (
auto *elm : std::as_const(elements)) {
497 elm->initSimulationVectors(elm->inputSize(), elm->outputSize());
501 buildConnectionGraph(elements);
502 connectWirelessElements(elements);
505 for (
auto *elm : std::as_const(elements)) {
506 if (elm->elementType() == ElementType::IC) {
507 static_cast<IC *
>(elm)->initializeSimulation();
512 sortSimElements(elements);
516 collectSequentialElements(elements);
518 m_initialized =
true;
520 qCDebug(zero) <<
"Finished simulation layer.";
528 for (
auto *elm : std::as_const(elements)) {
529 for (
int i = 0; i < elm->inputSize(); ++i) {
530 auto *inputPort = elm->inputPort(i);
531 const auto &connections = inputPort->connections();
533 if (connections.size() == 1) {
534 auto *connection = connections.constFirst();
538 if (
auto *outputPort = connection->startPort()) {
539 auto *sourceElement = outputPort->graphicElement();
541 elm->connectPredecessor(i, sourceElement, outputPort->index());
556 for (
auto *elm : std::as_const(elements)) {
557 if (elm->wirelessMode() != WirelessMode::Rx || elm->label().isEmpty()) {
560 if (
auto *txElement = txMap.value(elm->label(),
nullptr)) {
561 elm->connectPredecessor(0, txElement, 0);
568 QHash<QString, GraphicElement *> txMap;
569 for (
auto *elm : std::as_const(elements)) {
570 if (elm->wirelessMode() == WirelessMode::Tx && !elm->label().isEmpty()) {
571 if (!txMap.contains(elm->label())) {
572 txMap.insert(elm->label(), elm);
580 const QVector<GraphicElement *> &elements,
581 const QHash<QString, GraphicElement *> &txMap)
583 QHash<GraphicElement *, QVector<GraphicElement *>> successors;
586 for (
auto *elm : std::as_const(elements)) {
587 for (
auto *outputPort : elm->outputs()) {
588 for (
auto *conn : outputPort->connections()) {
589 if (
auto *endPort = conn->endPort()) {
590 auto *successor = endPort->graphicElement();
592 auto &vec = successors[elm];
593 if (!vec.contains(successor)) {
594 vec.append(successor);
607 for (
auto *elm : std::as_const(elements)) {
608 if (elm->wirelessMode() == WirelessMode::Rx && !elm->label().isEmpty()) {
609 if (
auto *tx = txMap.value(elm->label(),
nullptr)) {
610 auto &txVec = successors[tx];
611 if (!txVec.contains(elm)) {
622 const QVector<GraphicElement *> &elements,
623 const QHash<
GraphicElement *, QVector<GraphicElement *>> &successors)
627 QVector<GraphicElement *> rawPtrs(elements);
632 std::stable_sort(result.
sorted.begin(), result.
sorted.end(),
633 [&result](
const auto *a,
const auto *b) {
634 return result.priorities.value(const_cast<GraphicElement *>(a), -1)
635 > result.priorities.value(const_cast<GraphicElement *>(b), -1);
643 for (
int iteration = 0; iteration < maxIterations; ++iteration) {
644 for (
auto *element : std::as_const(elements)) {
648 element->clearOutputChanged();
649 element->updateLogic();
652 const bool converged = std::none_of(elements.cbegin(), elements.cend(),
653 [](
const auto *element) { return element && element->outputChanged(); });
662void Simulation::sortSimElements(
const QVector<GraphicElement *> &elements)
668 m_simPriorities.clear();
669 m_simFeedbackNodes.clear();
670 for (
auto *elm : std::as_const(elements)) {
671 m_simPriorities[elm] = result.priorities.value(elm, -1);
672 if (result.feedbackNodes.contains(elm)) {
673 m_simFeedbackNodes.insert(elm);
676 m_simHasFeedbackElements = !m_simFeedbackNodes.isEmpty();
677 m_sortedElements = result.sorted;
680void Simulation::collectSequentialElements(
const QVector<GraphicElement *> &elements)
682 for (
auto *elm : std::as_const(elements)) {
686 if (elm->elementGroup() == ElementGroup::Memory) {
687 m_sequentialElements.append(elm);
689 if (elm->elementType() == ElementType::IC) {
690 collectSequentialElements(
static_cast<IC *
>(elm)->internalElements());
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)
Connection: a wire that connects an output port to an input port in the circuit scene.
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).
void calculatePriorities(const QVector< T * > &elements, const QHash< T *, QVector< T * > > &successors, QHash< T *, int > &outPriorities)
Priority calculation for directed graphs.
Interface exposing the narrow slice of Scene that Simulation depends on.
Synchronous cycle-based simulation engine with event-driven clock support.
static void guardedSlot(const QObject *context, Body &&body) noexcept
Wraps a slot body in try/catch and reports any exception synchronously, inside the slot's own stack f...
static bool interactiveMode
Abstract base class for all graphical circuit elements in wiRedPanda.
Graphic element representing an Integrated Circuit (sub-circuit) box.
A port that drives a signal (the source end of a wire).
void setStatus(const Status status) override
GraphicElement * graphicElement()
Returns the graphic element that owns this port.
int index() const
Returns the port's visual/logical index within the element.
Status defaultValue() const
Returns the default status applied when the port is unconnected.
const QList< Connection * > & connections() const
Returns the list of wires attached to this port.
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.
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
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.
QSet< GraphicElement * > feedbackNodes
Elements in feedback loops.
QVector< GraphicElement * > sorted
Elements in priority order (highest first).
QHash< GraphicElement *, int > priorities
Priority per element.