wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
ICRegistry.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 <QCoreApplication>
7#include <QDataStream>
8#include <QDir>
9#include <QFileInfo>
10#include <QSaveFile>
11#include <QThread>
12
14#include "App/Core/Common.h"
16#include "App/Element/IC.h"
19#include "App/IO/VersionInfo.h"
20#include "App/Scene/Commands.h"
21#include "App/Scene/Scene.h"
24#include "App/Versions.h"
25
27 : QObject(scene)
28 , m_scene(scene)
29{
30 connect(&m_fileWatcher, &QFileSystemWatcher::fileChanged,
31 this, &ICRegistry::onFileChanged, Qt::QueuedConnection);
32}
33
34const QByteArray &ICRegistry::cachedFileBytes(const QString &filePath)
35{
36 Q_ASSERT(QCoreApplication::instance()->thread() == QThread::currentThread());
37
38 if (!m_fileCache.contains(filePath)) {
39 QFile file(filePath);
40 if (file.open(QIODevice::ReadOnly)) {
41 m_fileCache[filePath] = file.readAll();
42 } else {
43 // Do NOT insert into m_fileCache on failure: QMap::operator[] would
44 // default-construct and permanently cache an empty entry, silently
45 // masking the failure on every subsequent lookup (including if the
46 // file becomes readable later).
47 qCWarning(zero) << "ICRegistry: cannot open IC file:" << filePath;
48 static const QByteArray empty;
49 return empty;
50 }
51 }
52 return m_fileCache[filePath];
53}
54
55void ICRegistry::invalidate(const QString &filePath)
56{
57 m_fileCache.remove(filePath);
58}
59
60void ICRegistry::watchFile(const QString &filePath)
61{
62 if (!m_fileWatcher.files().contains(filePath)) {
63 m_fileWatcher.addPath(filePath);
64 }
65}
66
67QList<GraphicElement *> ICRegistry::findICsByFile(const QString &fileName) const
68{
69 const QFileInfo target(fileName);
70 QList<GraphicElement *> result;
71 for (auto *elm : m_scene->elements()) {
72 if (elm->elementType() == ElementType::IC) {
73 auto *ic = static_cast<IC *>(elm);
74 if (QFileInfo(ic->file()) == target) {
75 result.append(elm);
76 }
77 }
78 }
79 return result;
80}
81
82void ICRegistry::onFileChanged(const QString &filePath)
83{
84 // Guarded because this is a queued slot that deliberately rethrows below, so an escaping
85 // exception crosses Qt's signal-slot dispatch -- where the upstream catch in
86 // Application::notify cannot be relied on. On macOS it is structurally unreachable (Qt's
87 // Exception Safety documentation, and QTBUG-15197): the unwinder reaches std::terminate
88 // mid-stack before any upstream frame runs. Debian's own buildd reproduces the same abort
89 // natively on armhf, hppa and sparc64 with Qt 6.10.2 (issue #525) -- no emulation
90 // involved -- while amd64 catches the same throw and carries on. Only the behaviour is
91 // established off amd64, not the mechanism. Deleting a watched IC file therefore
92 // terminated the application on those platforms instead of reporting the load error.
93 Application::guardedSlot(this, [this, &filePath] {
94 qCDebug(zero) << "IC file changed:" << filePath;
95
96 // Invalidate the cached definition so it's rebuilt on next access
97 invalidate(filePath);
98
99 // Re-add the watch (some OS remove it after a file change event)
100 if (!m_fileWatcher.files().contains(filePath) && QFileInfo::exists(filePath)) {
101 m_fileWatcher.addPath(filePath);
102 }
103
104 // Reload all IC instances referencing this file
105 const auto targets = findICsByFile(filePath);
106 if (targets.isEmpty()) {
107 emit definitionChanged(filePath);
108 return;
109 }
110
111 // Capture pre-reload state so the undo command can restore both the
112 // ICs' element data and the scene wires that touch their ports.
113 // Without this the wires get cascade-deleted by setInputSize/setOutputSize
114 // inside loadFile and Cluster D throws on the next undo lookup.
115 const auto connections = UpdateBlobCommand::captureConnections(targets);
116 const QByteArray oldData = captureSnapshot(targets);
117
118 try {
119 reloadTargetsAtomically(targets, oldData, [&](IC *ic) { ic->loadFile(filePath); });
120 } catch (...) {
121 m_scene->setCircuitUpdateRequired();
122 emit definitionChanged(filePath);
123 throw;
124 }
125 m_scene->setCircuitUpdateRequired();
126
127 auto *cmd = new UpdateBlobCommand(targets, oldData, connections, m_scene);
128 m_scene->undoStack()->push(cmd);
129
130 emit definitionChanged(filePath);
131 });
132}
133
134// --- Embedded IC blob storage ---
135
136bool ICRegistry::hasBlob(const QString &name) const
137{
138 return m_blobs.contains(name);
139}
140
141QByteArray ICRegistry::blob(const QString &name) const
142{
143 return m_blobs.value(name);
144}
145
146void ICRegistry::setBlob(const QString &name, const QByteArray &data)
147{
148 m_blobs[name] = data;
149}
150
151void ICRegistry::registerBlob(const QString &name, const QByteArray &data)
152{
153 QSet<QString> visited;
154 QMap<QString, QByteArray> workingBlobs;
155 workingBlobs[name] = data;
156 makeBlobSelfContained(name, visited, workingBlobs);
157 m_blobs[name] = workingBlobs[name];
158}
159
160void ICRegistry::removeBlob(const QString &name)
161{
162 m_blobs.remove(name);
163}
164
165void ICRegistry::renameBlob(const QString &oldName, const QString &newName)
166{
167 // Defense in depth: the primary guard is at the caller (ElementEditor rejects a colliding
168 // rename before ever constructing a command), but no-op here too rather than silently
169 // overwriting an unrelated blob's bytes, so any future/other caller can't corrupt one IC's
170 // data by renaming a different one onto it.
171 if (!m_blobs.contains(oldName) || oldName == newName || m_blobs.contains(newName)) {
172 return;
173 }
174
175 m_blobs[newName] = m_blobs.take(oldName);
176
177 // Update all IC instances on the scene referencing the old name
178 for (auto *elm : m_scene->elements()) {
179 if (elm->isEmbedded() && elm->blobName() == oldName) {
180 auto *ic = static_cast<IC *>(elm);
181 ic->setBlobName(newName);
182 }
183 }
184
185 // Update embedded IC references inside other blobs' metadata so that parent
186 // blobs that contain the renamed blob as a nested dependency stay consistent.
187 for (auto it = m_blobs.begin(); it != m_blobs.end(); ++it) {
188 renameBlobReference(it.value(), oldName, newName);
189 }
190
191 emit blobRenamed(oldName, newName);
192}
193
195{
196 m_blobs.clear();
197}
198
199QList<GraphicElement *> ICRegistry::findICsByBlobName(const QString &blobName) const
200{
201 QList<GraphicElement *> result;
202 for (auto *elm : m_scene->elements()) {
203 if (elm->isEmbedded() && elm->blobName() == blobName) {
204 result.append(elm);
205 }
206 }
207 return result;
208} // LCOV_EXCL_LINE — recurring pattern 1: compiler-generated cleanup for the returned QList<GraphicElement *>, never reached after the return above.
209
210bool ICRegistry::initEmbeddedIC(IC *ic, const QString &blobName)
211{
212 if (!hasBlob(blobName)) {
213 return false;
214 }
215 ic->setBlobName(blobName);
216 ic->loadFromBlob(blob(blobName), m_scene->contextDir());
217 if (ic->label().isEmpty()) {
218 ic->setLabel(blobName.toUpper());
219 }
220 return true;
221}
222
223QString ICRegistry::uniqueBlobName(const QString &baseName) const
224{
225 if (!hasBlob(baseName)) {
226 return baseName;
227 }
228 for (int i = 2; ; ++i) {
229 const QString candidate = baseName + "_" + QString::number(i);
230 if (!hasBlob(candidate)) {
231 return candidate;
232 }
233 }
234}
235
236IC *ICRegistry::createEmbeddedIC(const QString &blobName, const QByteArray &fileBytes, const QString &contextDir)
237{
238 auto *ic = new IC();
239 ic->setBlobName(blobName);
240
241 m_scene->undoStack()->beginMacro(QCoreApplication::tr("Add embedded IC"));
242 m_scene->receiveCommand(new RegisterBlobCommand(blobName, fileBytes, m_scene));
243 ic->loadFromBlob(fileBytes, contextDir);
244 m_scene->receiveCommand(new AddItemsCommand({ic}, m_scene));
245 m_scene->undoStack()->endMacro();
246
247 return ic;
248}
249
250int ICRegistry::embedICsByFile(const QString &fileName, const QByteArray &fileBytes,
251 const QString &blobName)
252{
253 const auto targets = findICsByFile(fileName);
254 if (targets.isEmpty()) {
255 return 0;
256 }
257
258 const auto connections = UpdateBlobCommand::captureConnections(targets);
259 const QByteArray oldData = captureSnapshot(targets);
260
261 registerBlob(blobName, fileBytes);
262
263 try {
264 reloadTargetsAtomically(targets, oldData, [&](IC *ic) {
265 // loadFromBlob() first: if it throws, the element must stay fully untouched
266 // (reloadTargetsAtomically only rolls back targets whose mutate() call
267 // completed) -- setting the blob name first would leave a throwing element
268 // half-mutated (isEmbedded() flipped true, blobName changed) with no rollback.
269 ic->loadFromBlob(m_blobs[blobName], m_scene->contextDir());
270 ic->setBlobName(blobName);
271 });
272 } catch (...) {
273 removeBlob(blobName);
274 throw;
275 }
276
277 auto *cmd = new UpdateBlobCommand(targets, oldData, connections, m_scene);
278 // This blob is newly registered above (registerBlob() at a name these targets weren't
279 // already using), not replacing prior content — leave m_oldBlob at its default-empty so
280 // undo() removes it rather than restoring bytes that never existed. Explicit rather than
281 // relying on the constructor default, unlike every other UpdateBlobCommand call site.
282 cmd->setOldBlob(QByteArray());
283 m_scene->undoStack()->push(cmd);
284 return static_cast<int>(targets.size());
285}
286
287int ICRegistry::extractToFile(const QString &blobName, const QString &filePath)
288{
289 // Write blob to disk
290 QSaveFile saveFile(filePath);
291 if (!saveFile.open(QIODevice::WriteOnly)) {
292 throw PANDACEPTION("Could not open file: %1", saveFile.errorString());
293 }
294 saveFile.write(blob(blobName));
295 if (!saveFile.commit()) {
296 // Covered: TestICRegistry::testExtractToFileThrowsWhenCommitFails() forces this via
297 // RLIMIT_FSIZE (ScopedTinyFsizeLimit) -- Qt defers write() errors, so a failed
298 // write() above only surfaces here, at commit().
299 throw PANDACEPTION("Could not save file: %1", saveFile.errorString());
300 }
301
302 // Convert all embedded ICs with this blobName to file-backed
303 const auto targets = findICsByBlobName(blobName);
304 if (targets.isEmpty()) {
305 return 0;
306 }
307
308 const auto connections = UpdateBlobCommand::captureConnections(targets);
309 const QByteArray oldData = captureSnapshot(targets);
310 const QByteArray oldBlob = blob(blobName);
311
312 const QString fileDir = QFileInfo(filePath).absolutePath();
313 reloadTargetsAtomically(targets, oldData, [&](IC *ic) { ic->loadFile(filePath, fileDir); });
314
315 removeBlob(blobName);
316
317 auto *cmd = new UpdateBlobCommand(targets, oldData, connections, m_scene);
318 cmd->setOldBlob(oldBlob);
319 cmd->setBlobName(blobName);
320 m_scene->undoStack()->push(cmd);
321 return static_cast<int>(targets.size());
322}
323
324void ICRegistry::rollbackElements(const QList<GraphicElement *> &elements, const QByteArray &snapshot,
325 Scene *scene)
326{
327 QByteArray data(snapshot);
328 QDataStream stream(&data, QIODevice::ReadOnly);
329 const auto version = Serialization::readPandaHeader(stream);
330 QHash<quint64, Port *> portMap;
331 auto ctx = scene->deserializationContext(portMap, version, SerializationPurpose::InMemorySnapshot);
332 for (auto *elm : elements) {
333 elm->load(stream, ctx); // LCOV_EXCL_LINE — reloadTargetsAtomically()'s three current callers (onFileChanged/embedICsByFile/extractToFile) all apply the SAME file or blob bytes to every target in a batch, and a parse failure depends only on those bytes/contextDir, never on which target element is being mutated -- so within one batch either every target's mutate() call succeeds or the very first one throws. "elements" (the already-mutated subset needing rollback) can therefore never be non-empty in practice; kept for the case where a future caller passes heterogeneous per-target mutations.
334 }
335}
336
337void ICRegistry::reloadTargetsAtomically(const QList<GraphicElement *> &targets, const QByteArray &oldData,
338 const std::function<void(IC *)> &mutate)
339{
340 // Stop the simulation for the entire loop, not just each individual mutation. Between
341 // freeing one IC's old internal graph and rebuilding it, the scene's sorted vectors hold
342 // dangling pointers; ticking on that state would fault — and that stale state persists
343 // across the whole loop, since setCircuitUpdateRequired() only runs once, after every
344 // target has been reloaded, not after each one.
345 QList<GraphicElement *> updated;
346 SimulationBlocker blocker(m_scene->simulation());
347 try {
348 for (auto *elm : targets) {
349 mutate(static_cast<IC *>(elm));
350 updated.append(elm);
351 }
352 } catch (...) {
353 rollbackElements(updated, oldData, m_scene);
354 throw;
355 }
356}
357
358void ICRegistry::makeBlobSelfContained(const QString &name, QSet<QString> &visited,
359 QMap<QString, QByteArray> &blobs, int depth)
360{
361 if (depth >= kMaxBlobNestingDepth) {
362 throw PANDACEPTION("Embedded IC dependency chain exceeds the maximum nesting depth (%1) while resolving '%2'",
363 QString::number(kMaxBlobNestingDepth), name);
364 }
365
366 if (visited.contains(name)) {
367 qCWarning(zero) << "Circular blob reference detected:" << name << "— skipping.";
368 return;
369 }
370 visited.insert(name);
371
372 QByteArray blobData(blobs[name]);
373 QDataStream readStream(&blobData, QIODevice::ReadOnly);
374 const auto preamble = Serialization::readPreamble(readStream);
375
376 if (!VersionInfo::hasMetadata(preamble.version)) {
377 return;
378 }
379
380 auto metadata = preamble.metadata;
381 auto embeddedICs = Serialization::deserializeBlobRegistry(metadata, preamble.version);
382 bool modified = false;
383
384 // Recurse into already-embedded blobs to ensure they are self-contained
385 for (auto it = embeddedICs.begin(); it != embeddedICs.end(); ++it) {
386 const QString &depName = it.key();
387 blobs[depName] = it.value();
388 makeBlobSelfContained(depName, visited, blobs, depth + 1);
389 it.value() = blobs[depName];
390 }
391
392 // Resolve file-backed IC dependencies from disk and embed them
393 if (metadata.contains("fileBackedICs")) {
394 const QStringList files = metadata.value("fileBackedICs").toStringList();
395 const QString contextDir = m_scene->contextDir();
396
397 for (const QString &fileName : files) {
398 const QString baseName = QFileInfo(fileName).baseName();
399 if (embeddedICs.contains(baseName)) {
400 continue;
401 }
402
403 QFileInfo fi(QDir(contextDir), fileName);
404 if (!fi.exists()) {
405 qCWarning(zero) << "makeBlobSelfContained: dependency" << fileName << "not found for blob" << name << "— skipping.";
406 continue;
407 }
408
409 QFile file(fi.absoluteFilePath());
410 if (!file.open(QIODevice::ReadOnly)) {
411 qCWarning(zero) << "makeBlobSelfContained: cannot open dependency" << fi.absoluteFilePath() << "for blob" << name << "— blob will be incomplete.";
412 continue;
413 }
414 QByteArray fileBytes = file.readAll();
415 file.close();
416
417 // Recursively make the dep self-contained before embedding
418 blobs[baseName] = fileBytes;
419 makeBlobSelfContained(baseName, visited, blobs, depth + 1);
420 embeddedICs[baseName] = blobs[baseName];
421 }
422
423 metadata.remove("fileBackedICs");
424 modified = true;
425 }
426
427 if (!modified) {
428 return;
429 }
430
431 // Re-serialize the blob with updated metadata, preserving the elements/connections
432 // tail byte-for-byte. Read that tail from the already-decompressed
433 // preamble.remainingPayload, not readStream's device -- readPreamble() fully
434 // consumed the live device to do that decompression, so the device has
435 // nothing left to offer here.
436 const QByteArray elements = preamble.remainingPayload;
437
438 Serialization::serializeBlobRegistry(embeddedICs, metadata);
439
440 // Metadata and the elements tail must be compressed together as one payload
441 // (see Serialization::writePayload()), not written as two independent raw
442 // writes -- readPreamble() on the other end decompresses the whole thing in
443 // one shot.
444 QByteArray payload;
445 QDataStream payloadStream(&payload, QIODevice::WriteOnly);
446 payloadStream.setVersion(QDataStream::Qt_5_12);
447 payloadStream << metadata;
448 payloadStream.writeRawData(elements.constData(), static_cast<int>(elements.size()));
449
450 QByteArray newBlob;
451 QDataStream writeStream(&newBlob, QIODevice::WriteOnly);
453 Serialization::writePayload(writeStream, payload);
454
455 blobs[name] = newBlob;
456}
457
458void ICRegistry::renameBlobReference(QByteArray &blobData, const QString &oldName, const QString &newName)
459{
460 QDataStream readStream(&blobData, QIODevice::ReadOnly);
461 const auto preamble = Serialization::readPreamble(readStream);
462
463 if (!VersionInfo::hasMetadata(preamble.version)) {
464 return;
465 }
466
467 auto embeddedICs = Serialization::deserializeBlobRegistry(preamble.metadata, preamble.version);
468 if (!embeddedICs.contains(oldName)) {
469 return;
470 }
471
472 // Rename the key in the embedded IC map
473 embeddedICs[newName] = embeddedICs.take(oldName);
474
475 // Re-serialize the blob with updated metadata, preserving the elements/connections
476 // tail byte-for-byte. Read that tail from the already-decompressed
477 // preamble.remainingPayload, not readStream's device -- readPreamble() fully
478 // consumed the live device to do that decompression, so the device has
479 // nothing left to offer here.
480 const QByteArray elements = preamble.remainingPayload;
481 auto metadata = preamble.metadata;
482 Serialization::serializeBlobRegistry(embeddedICs, metadata);
483
484 // Metadata and the elements tail must be compressed together as one payload
485 // (see Serialization::writePayload()), not written as two independent raw
486 // writes -- readPreamble() on the other end decompresses the whole thing in
487 // one shot.
488 QByteArray payload;
489 QDataStream payloadStream(&payload, QIODevice::WriteOnly);
490 payloadStream.setVersion(QDataStream::Qt_5_12);
491 payloadStream << metadata;
492 payloadStream.writeRawData(elements.constData(), static_cast<int>(elements.size()));
493
494 QByteArray newBlob;
495 QDataStream writeStream(&newBlob, QIODevice::WriteOnly);
497 Serialization::writePayload(writeStream, payload);
498
499 blobData = newBlob;
500}
501
502QByteArray ICRegistry::captureSnapshot(const QList<GraphicElement *> &targets)
503{
504 QByteArray data;
505 QDataStream stream(&data, QIODevice::WriteOnly);
507 for (auto *elm : targets) {
508 elm->save(stream, {.purpose = SerializationPurpose::InMemorySnapshot});
509 }
510 return data;
511}
Custom QApplication subclass with exception handling and main-window access.
All QUndoCommand subclasses and the CommandUtils helper namespace.
Common logging utilities, the Pandaception error type, and helper macros.
#define PANDACEPTION(msg,...)
Definition Common.h:98
#define qCDebug(category)
Definition Common.h:29
Abstract base class for all graphical circuit elements.
IC definition registry with file watching and embedded blob storage.
Integrated Circuit (IC) graphic element that encapsulates a sub-circuit file.
Main circuit editing scene with undo/redo and user interaction.
Deserialization/serialization context structs passed through load()/save() call chains.
Circuit and waveform file serialization/deserialization utilities.
RAII guard that temporarily stops the simulation while in scope.
Synchronous cycle-based simulation engine with event-driven clock support.
Named version predicates for file-format compatibility checks.
File-format version constants and application version accessor.
Undo command that adds a list of graphic elements to the scene.
Definition Commands.h:67
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...
QString label() const
Returns the user-visible label text for this element.
void setLabel(const QString &label)
Sets the label text to label and refreshes the display.
void renameBlob(const QString &oldName, const QString &newName)
Renames a blob from oldName to newName, updating the cache key.
void setBlob(const QString &name, const QByteArray &data)
Stores or replaces the blob data under name and invalidates the cached definition.
ICRegistry(Scene *scene)
int embedICsByFile(const QString &fileName, const QByteArray &fileBytes, const QString &blobName)
Converts all file-backed IC elements referencing fileName to embedded ICs using blobName.
bool initEmbeddedIC(IC *ic, const QString &blobName)
Initializes an embedded IC by looking up its blob in the registry.
QByteArray blob(const QString &name) const
Returns the raw .panda bytes for the embedded IC named name.
void clearBlobs()
Removes all stored blobs.
QString uniqueBlobName(const QString &baseName) const
Returns baseName if available, or appends a numeric suffix to avoid collision.
QList< GraphicElement * > findICsByBlobName(const QString &blobName) const
Finds all embedded IC elements with blobName.
int extractToFile(const QString &blobName, const QString &filePath)
Writes the blob to disk and converts all embedded ICs with blobName to file-backed.
void invalidate(const QString &filePath)
Invalidates a cached definition (e.g., after file change).
const QByteArray & cachedFileBytes(const QString &filePath)
Returns cached file bytes, reading from disk on first access. Returns empty on failure.
void definitionChanged(const QString &filePath)
Emitted when an IC definition file changes on disk and its cached definition is invalidated.
void blobRenamed(const QString &oldName, const QString &newName)
void removeBlob(const QString &name)
Removes the blob named name from the registry.
IC * createEmbeddedIC(const QString &blobName, const QByteArray &fileBytes, const QString &contextDir)
Creates a new embedded IC from file bytes, registers the blob, and pushes an undo command.
QList< GraphicElement * > findICsByFile(const QString &fileName) const
Finds all IC elements in the scene that reference fileName.
static void rollbackElements(const QList< GraphicElement * > &elements, const QByteArray &snapshot, Scene *scene)
Restores elements from a previously captured snapshot (used for atomic rollback).
void registerBlob(const QString &name, const QByteArray &data)
Stores blob data under name without invalidating the definition cache.
void watchFile(const QString &filePath)
Registers a file for watching. Called when an IC element is added to the scene.
bool hasBlob(const QString &name) const
Returns true if a blob named name is stored in the registry.
static QByteArray captureSnapshot(const QList< GraphicElement * > &targets)
Serializes targets into a self-contained .panda byte array (used for embedding).
Graphic element representing an Integrated Circuit (sub-circuit) box.
Definition IC.h:31
void loadFromBlob(const QByteArray &blob, const QString &contextDir)
Loads the IC from in-memory blob bytes (full .panda file format).
Definition IC.cpp:248
void setBlobName(const QString &name)
Definition IC.h:81
void loadFile(const QString &fileName, const QString &contextDir={})
Loads the IC circuit from fileName and rebuilds the logic mapping.
Definition IC.cpp:243
Undo command that registers/unregisters a blob in the IC registry.
Definition Commands.h:450
Main circuit editing scene.
Definition Scene.h:56
SerializationContext deserializationContext(QHash< quint64, Port * > &portMap, const QVersionNumber &version, SerializationPurpose purpose)
Definition Scene.cpp:216
Simulation * simulation()
Returns the simulation engine associated with this scene.
Definition Scene.cpp:288
static void writePandaHeader(QDataStream &stream)
Writes the .panda circuit file header to stream.
static void serializeBlobRegistry(const QMap< QString, QByteArray > &blobs, QMap< QString, QVariant > &metadata)
Serializes embedded ICs into a metadata map (sets the "embeddedICs" key).
static QVersionNumber readPandaHeader(QDataStream &stream)
Reads and validates the .panda circuit file header; returns the stored version number.
static void writePayload(QDataStream &stream, const QByteArray &payload)
Compresses payload (qCompress) and writes it to stream.
static Preamble readPreamble(QDataStream &stream)
Reads the full .panda preamble: header, dolphin filename, rect, and metadata (V_4_5+).
static QMap< QString, QByteArray > deserializeBlobRegistry(const QMap< QString, QVariant > &metadata, const QVersionNumber &fileVersion)
Extracts the embedded IC registry from a metadata map.
RAII guard that stops the simulation on construction and restarts it on destruction.
Undo command for embedded IC blob changes that may alter port counts.
Definition Commands.h:521
static QList< ConnectionInfo > captureConnections(const QList< GraphicElement * > &targets)
Captures connection topology for all target elements before a blob operation.
bool hasMetadata(const QVersionNumber &v)
V4.5: File-level metadata map and embedded IC blob registry.
Definition VersionInfo.h:72