8#include <QGraphicsView>
9#include <QGuiApplication>
22using namespace std::chrono_literals;
30 m_timer.setInterval(1ms);
35 if (
auto *screen = QGuiApplication::primaryScreen()) {
36 const qreal hz = screen->refreshRate();
38 m_visualTickInterval = (std::max)(1,
static_cast<int>(1000.0 / hz));
45 m_visualThrottleEnabled = enabled;
47 m_visualTickCount = 0;
64 Q_ASSERT(m_initialized);
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;
80 if (m_timer.isActive()) {
81 const auto globalTime = std::chrono::steady_clock::now();
83 for (
auto *clock : clocks) {
85 clock->updateClock(globalTime);
91 for (
auto *inputElm : inputs) {
93 inputElm->updateOutputs();
101 || (++m_visualTickCount >= m_visualTickInterval);
103 m_visualTickCount = 0;
113 bool sourceChanged =
false;
114 for (
auto *clock : clocks) {
115 if (clock && clock->outputChanged()) {
116 sourceChanged =
true;
117 clock->clearOutputChanged();
120 for (
auto *inputElm : inputs) {
121 if (inputElm && inputElm->outputChanged()) {
122 sourceChanged =
true;
123 inputElm->clearOutputChanged();
127 if (!sourceChanged && m_atFixedPoint) {
128 if (visualsDue && m_visualsDirty) {
129 pushVisualStatuses(elements, outputs);
130 m_visualsDirty =
false;
145 for (
auto *element : sequentialElements) {
147 element->beginDeferredCommit();
153 bool sweepConverged =
true;
155 if (m_simHasFeedbackElements) {
157 sweepConverged = updateWithIterativeSettling(elements);
161 for (
auto *element : elements) {
163 element->updateLogic();
169 bool anySequentialChanged =
false;
170 for (
auto *element : sequentialElements) {
172 element->clearOutputChanged();
173 element->commitDeferredOutputs();
174 if (element->outputChanged()) {
175 anySequentialChanged =
true;
189 if (anySequentialChanged) {
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();
203 if (pass == maxPasses - 1) {
205 sweepConverged =
false;
212 m_atFixedPoint = sweepConverged;
213 m_visualsDirty =
true;
220 pushVisualStatuses(elements, outputs);
221 m_visualsDirty =
false;
225void Simulation::pushVisualStatuses(
const QVector<GraphicElement *> &elements,
const QVector<GraphicElement *> &outputs)
231 for (
auto *element : elements) {
233 for (
auto *outputPort : element->outputs()) {
234 updatePort(outputPort);
240 for (
auto *outputElm : outputs) {
242 for (
auto *inputPort : outputElm->inputs()) {
244 updatePort(inputPort);
266void Simulation::updatePort(
InputPort *port)
273 const Status status = (!conns.isEmpty() && conns.first()->startPort())
274 ? conns.first()->startPort()->status()
280 if (elm && elm->elementGroup() == ElementGroup::Output) {
293 m_initialized =
false;
296 m_atFixedPoint =
false;
297 m_visualsDirty =
true;
298 m_sortedElements.clear();
299 m_sequentialElements.clear();
306 Q_ASSERT(!m_initialized);
307 Q_ASSERT(m_sortedElements.isEmpty() && m_sequentialElements.isEmpty()
308 && m_clocks.isEmpty() && m_inputs.isEmpty() && m_outputs.isEmpty());
313 return m_timer.isActive();
318 return m_simFeedbackNodes.contains(element);
325 m_host->setMuted(
true);
331 qCDebug(zero) <<
"Starting simulation.";
333 if (!m_initialized) {
338 const auto globalTime = std::chrono::steady_clock::now();
339 for (
auto *clock : std::as_const(m_clocks)) {
341 clock->resetClock(globalTime);
348 m_host->setMuted(m_userMuted);
350 qCDebug(zero) <<
"Simulation started.";
357 m_host->setMuted(muted);
366bool Simulation::updateWithIterativeSettling(
const QVector<GraphicElement *> &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."));
385 m_convergenceWarned =
false;
389 m_sortedElements.clear();
390 m_sequentialElements.clear();
392 QVector<GraphicElement *> elements;
393 auto items = m_host->simulationItems();
399 std::stable_sort(items.begin(), items.end(), [](
const auto &a,
const auto &b) {
404 if (qFuzzyCompare(a->y(), b->y())) {
405 return a->x() < b->x();
407 return a->y() < b->y();
412 if (items.size() == 1) {
416 qCDebug(two) <<
"GENERATING SIMULATION LAYER.";
418 const auto globalTime = std::chrono::steady_clock::now();
420 for (
auto *item : std::as_const(items)) {
426 auto *element = qgraphicsitem_cast<GraphicElement *>(item);
430 elements.append(element);
432 if (element->elementType() == ElementType::Clock) {
433 auto *clock = qobject_cast<Clock *>(element);
435 m_clocks.append(clock);
436 clock->resetClock(globalTime);
440 if (element->elementGroup() == ElementGroup::Input) {
441 auto *input = qobject_cast<GraphicElementInput *>(element);
443 m_inputs.append(input);
447 if (element->elementGroup() == ElementGroup::Output) {
448 m_outputs.append(element);
453 qCDebug(zero) <<
"Elements read: " << elements.size();
455 if (elements.empty()) {
460 for (
auto *elm : std::as_const(elements)) {
461 elm->initSimulationVectors(elm->inputSize(), elm->outputSize());
465 buildConnectionGraph(elements);
466 connectWirelessElements(elements);
469 for (
auto *elm : std::as_const(elements)) {
470 if (elm->elementType() == ElementType::IC) {
471 static_cast<IC *
>(elm)->initializeSimulation();
476 sortSimElements(elements);
480 collectSequentialElements(elements);
482 m_initialized =
true;
484 qCDebug(zero) <<
"Finished simulation layer.";
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();
497 if (connections.size() == 1) {
498 auto *connection = connections.constFirst();
502 if (
auto *outputPort = connection->startPort()) {
503 auto *sourceElement = outputPort->graphicElement();
505 elm->connectPredecessor(i, sourceElement, outputPort->index());
520 for (
auto *elm : std::as_const(elements)) {
521 if (elm->wirelessMode() != WirelessMode::Rx || elm->label().isEmpty()) {
524 if (
auto *txElement = txMap.value(elm->label(),
nullptr)) {
525 elm->connectPredecessor(0, txElement, 0);
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);
544 const QVector<GraphicElement *> &elements,
545 const QHash<QString, GraphicElement *> &txMap)
547 QHash<GraphicElement *, QVector<GraphicElement *>> successors;
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();
556 auto &vec = successors[elm];
557 if (!vec.contains(successor)) {
558 vec.append(successor);
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)) {
586 const QVector<GraphicElement *> &elements,
587 const QHash<
GraphicElement *, QVector<GraphicElement *>> &successors)
591 QVector<GraphicElement *> rawPtrs(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);
607 for (
int iteration = 0; iteration < maxIterations; ++iteration) {
608 for (
auto *element : std::as_const(elements)) {
612 element->clearOutputChanged();
613 element->updateLogic();
616 const bool converged = std::none_of(elements.cbegin(), elements.cend(),
617 [](
const auto *element) { return element && element->outputChanged(); });
626void Simulation::sortSimElements(
const QVector<GraphicElement *> &elements)
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);
640 m_simHasFeedbackElements = !m_simFeedbackNodes.isEmpty();
641 m_sortedElements = result.sorted;
644void Simulation::collectSequentialElements(
const QVector<GraphicElement *> &elements)
646 for (
auto *elm : std::as_const(elements)) {
650 if (elm->elementGroup() == ElementGroup::Memory) {
651 m_sequentialElements.append(elm);
653 if (elm->elementType() == ElementType::IC) {
654 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 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.