More fixes.

Took 6 minutes

Took 26 seconds

Took 8 minutes
This commit is contained in:
Lukas Brübach 2026-07-27 21:04:31 +02:00
parent aebb7f599b
commit a6a164e33e
11 changed files with 80 additions and 34 deletions

View file

@ -264,9 +264,11 @@ int main(int argc, char *argv[])
// contention that happens when the load runs alongside window construction.
// The CardDatabaseModel populates from the already-loaded data in its
// constructor, so the window appears fully populated with no startup lag.
// Note: checkUnknownSets() is deferred until MainWindow::startupConfigCheck()
// so that cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled have live
// receivers when emitted.
// Note: checkUnknownSets() is deferred from the initial load to
// MainWindow::startupConfigCheck() so that
// cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled have live
// receivers when emitted. Subsequent reloads (e.g. path changes) call
// checkUnknownSets() directly from the loader after the first load.
CardDatabaseManager::getInstance()->loadCardDatabases();
MainWindow ui;

View file

@ -57,11 +57,22 @@ const QVariantHash &CardInfo::getPropertiesHash() const
void CardInfo::setProperty(const QString &_name, const QString &_value)
{
ensurePropertiesLoaded();
if (propertiesCache.value(_name).toString() == _value) {
return;
}
propertiesCache.insert(_name, _value);
propertiesBlob = serializeProperties(propertiesCache);
emit cardInfoChanged(smartThis);
}
void CardInfo::setProperties(const QVariantHash &_props)
{
ensurePropertiesLoaded();
propertiesCache = _props;
propertiesBlob = serializeProperties(propertiesCache);
emit cardInfoChanged(smartThis);
}
CardInfo::CardInfo(const QString &_name,
const QString &_text,
bool _isToken,
@ -201,13 +212,16 @@ void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo &_info)
void CardInfo::combineLegalities(const QVariantHash &props)
{
ensurePropertiesLoaded();
QHashIterator<QString, QVariant> it(props);
while (it.hasNext()) {
it.next();
if (it.key().startsWith("format-")) {
smartThis->setProperty(it.key(), it.value().toString());
propertiesCache.insert(it.key(), it.value());
}
}
propertiesBlob = serializeProperties(propertiesCache);
emit cardInfoChanged(smartThis);
}
void CardInfo::refreshCachedSets()

View file

@ -308,6 +308,7 @@ public:
return getPropertiesHash().value(propertyName).toString();
}
void setProperty(const QString &_name, const QString &_value);
void setProperties(const QVariantHash &_props);
[[nodiscard]] bool hasProperty(const QString &propertyName) const
{
return getPropertiesHash().contains(propertyName);

View file

@ -99,7 +99,9 @@ LoadStatus CardDatabaseLoader::doLoadCardDatabases()
LoadStatus loadStatus = NotLoaded;
// Try the binary cache first: a cache hit avoids re-parsing the (large) XML.
if (loadFromCache(data)) {
const QStringList customPaths = collectCustomDatabasePaths();
const QByteArray sourceHash = computeSourceHash(customPaths);
if (loadFromCache(data, sourceHash)) {
qCInfo(CardDatabaseLoadingLog) << "Loaded card database from binary cache";
loadStatus = Ok;
} else {
@ -110,30 +112,36 @@ LoadStatus CardDatabaseLoader::doLoadCardDatabases()
// find all custom card databases, recursively & following symlinks
// then load them alphabetically
const QStringList customPaths = collectCustomDatabasePaths();
for (int i = 0; i < customPaths.size(); ++i) {
const auto &p = customPaths.at(i);
qCInfo(CardDatabaseLoadingLog) << "Loading Custom Set" << i << "(" << p << ")";
loadCardDatabase(p, data);
}
saveToCache(data);
if (!saveToCache(data, sourceHash)) {
qCWarning(CardDatabaseLoadingLog) << "Failed to write binary cache to" << cachePath();
}
}
}
// AFTER all the cards have been loaded: resolve the reverse-related tags
// against the fully-built snapshot.
database->refreshCachedReverseRelatedCards(data.cards);
if (loadStatus == Ok) {
database->refreshCachedReverseRelatedCards(data.cards);
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Success";
emit databaseDataReady(std::move(data));
emit loadingFinished();
// NOTE: checkUnknownSets() is intentionally NOT called here. During
// the front-loaded parse in main() this runs before MainWindow exists,
// so cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled would be
// emitted with no receivers. MainWindow::startupConfigCheck() calls
// checkUnknownSets() once its signal connections are live.
// During the front-loaded parse in main() this runs before MainWindow
// exists, so cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled
// would be emitted with no receivers. Skip the check on the first load;
// MainWindow::startupConfigCheck() calls checkUnknownSets() once its
// signal connections are live. On subsequent reloads (e.g. path change)
// MainWindow exists and signals have live receivers.
if (initialLoadComplete) {
database->checkUnknownSets();
}
initialLoadComplete = true;
} else {
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed";
emit loadingFailed(); // bring up the settings dialog
@ -147,7 +155,7 @@ QString CardDatabaseLoader::cachePath() const
return pathProvider->getCardDatabasePath() + ".cache";
}
QByteArray CardDatabaseLoader::computeSourceHash() const
QByteArray CardDatabaseLoader::computeSourceHash(const QStringList &customPaths) const
{
// Hash over the paths, sizes and modification times of every input file so
// the cache invalidates when any source changes. Cheap (no content read).
@ -160,7 +168,7 @@ QByteArray CardDatabaseLoader::computeSourceHash() const
const QStringList inputs = QStringList()
<< pathProvider->getCardDatabasePath() << pathProvider->getTokenDatabasePath()
<< pathProvider->getSpoilerCardDatabasePath() << collectCustomDatabasePaths();
<< pathProvider->getSpoilerCardDatabasePath() << customPaths;
for (const QString &path : inputs) {
QFileInfo info(path);
if (info.exists()) {
@ -175,22 +183,20 @@ QByteArray CardDatabaseLoader::computeSourceHash() const
return hash.result();
}
bool CardDatabaseLoader::loadFromCache(CardDatabaseData &data)
bool CardDatabaseLoader::loadFromCache(CardDatabaseData &data, const QByteArray &sourceHash)
{
const QByteArray sourceHash = computeSourceHash();
if (sourceHash.isEmpty()) {
return false;
}
return CardDatabaseCache::read(cachePath(), data, sourceHash, priorityController);
}
void CardDatabaseLoader::saveToCache(const CardDatabaseData &data)
bool CardDatabaseLoader::saveToCache(const CardDatabaseData &data, const QByteArray &sourceHash)
{
const QByteArray sourceHash = computeSourceHash();
if (sourceHash.isEmpty()) {
return;
return false;
}
CardDatabaseCache::write(cachePath(), data, sourceHash);
return CardDatabaseCache::write(cachePath(), data, sourceHash);
}
QStringList CardDatabaseLoader::collectCustomDatabasePaths() const

View file

@ -135,7 +135,7 @@ private:
* @brief Computes a hash identifying the current set of input database files.
* @return Hash over the paths, sizes and modification times of all inputs.
*/
[[nodiscard]] QByteArray computeSourceHash() const;
[[nodiscard]] QByteArray computeSourceHash(const QStringList &customPaths) const;
/**
* @brief Path of the binary cache file derived from the main card database path.
@ -146,15 +146,18 @@ private:
/**
* @brief Attempts to populate the snapshot from the binary cache.
* @param data Snapshot to populate.
* @param sourceHash Pre-computed hash identifying the current input files.
* @return True if a valid, up-to-date cache was read.
*/
bool loadFromCache(CardDatabaseData &data);
bool loadFromCache(CardDatabaseData &data, const QByteArray &sourceHash);
/**
* @brief Writes the snapshot to the binary cache for the current inputs.
* @param data Snapshot to serialize.
* @param sourceHash Pre-computed hash identifying the current input files.
* @return True if the cache was written successfully.
*/
void saveToCache(const CardDatabaseData &data);
bool saveToCache(const CardDatabaseData &data, const QByteArray &sourceHash);
private:
CardDatabase *database; /**< Non-owning pointer to the target CardDatabase. */
@ -164,6 +167,7 @@ private:
QBasicMutex *loadFromFileMutex = new QBasicMutex(); /**< Mutex for single-file loading. */
QBasicMutex *reloadDatabaseMutex = new QBasicMutex(); /**< Mutex for reloading entire database. */
bool initialLoadComplete = false; /**< Set after the first successful load. */
};
#endif // COCKATRICE_CARD_DATABASE_LOADER_H

View file

@ -229,25 +229,27 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
// behaviour. Without this check, disabling a set has no effect on v3 databases.
if (set->getEnabled()) {
PrintingInfo setInfo(set);
QVariantHash printingProps;
if (attrs.hasAttribute("muId")) {
setInfo.setProperty("muid", attrs.value("muId").toString());
printingProps.insert("muid", attrs.value("muId").toString());
}
if (attrs.hasAttribute("uuId")) {
setInfo.setProperty("uuid", attrs.value("uuId").toString());
printingProps.insert("uuid", attrs.value("uuId").toString());
}
if (attrs.hasAttribute("picURL")) {
setInfo.setProperty("picurl", attrs.value("picURL").toString());
printingProps.insert("picurl", attrs.value("picURL").toString());
}
if (attrs.hasAttribute("num")) {
setInfo.setProperty("num", attrs.value("num").toString());
printingProps.insert("num", attrs.value("num").toString());
}
if (attrs.hasAttribute("rarity")) {
setInfo.setProperty("rarity", attrs.value("rarity").toString());
printingProps.insert("rarity", attrs.value("rarity").toString());
}
setInfo.setProperties(printingProps);
_sets[setName].append(setInfo);
}
// related cards

View file

@ -315,13 +315,15 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
auto set = internalAddSet(setName);
if (set->getEnabled()) {
PrintingInfo printingInfo(set);
QVariantHash printingProps;
for (QXmlStreamAttribute attr : attrs) {
QString attrName = attr.name().toString();
if (attrName == "picURL") {
attrName = "picurl";
}
printingInfo.setProperty(attrName, attr.value().toString());
printingProps.insert(attrName, attr.value().toString());
}
printingInfo.setProperties(printingProps);
// This is very much a hack and not the right place to
// put this check, as it requires a reload of Cockatrice

View file

@ -27,7 +27,17 @@ void PrintingInfo::ensurePropertiesLoaded() const
void PrintingInfo::setProperty(const QString &_name, const QString &_value)
{
ensurePropertiesLoaded();
if (propertiesCache.value(_name).toString() == _value) {
return;
}
propertiesCache.insert(_name, _value);
setProperties(propertiesCache);
}
void PrintingInfo::setProperties(const QVariantHash &_props)
{
ensurePropertiesLoaded();
propertiesCache = _props;
QDataStream out(&propertiesBlob, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_6_4);
out << propertiesCache;

View file

@ -127,6 +127,7 @@ public:
* @param _value The string value to assign.
*/
void setProperty(const QString &_name, const QString &_value);
void setProperties(const QVariantHash &_props);
/**
* @brief Stores the pre-serialized properties blob and marks the materialized

View file

@ -92,6 +92,7 @@ void CardDatabaseSettings::saveSets(const QVector<ICardSetPriorityController::Se
});
QMutexLocker lock(&setOptionsMutex);
ensureSetOptionsLoaded();
for (const auto &entry : data) {
SetOptions &o = setOptionsCache[entry.shortName];
o.sortKey = entry.sortKey;

View file

@ -291,12 +291,13 @@ int OracleImporter::importCardsFromSet(const CardSetPtr &currentSet, const QList
// per-set properties
PrintingInfo printingInfo = PrintingInfo(currentSet);
QVariantHash printingProps;
for (auto i = setInfoProperties.cbegin(), end = setInfoProperties.cend(); i != end; ++i) {
QString mtgjsonProperty = i.key();
QString xmlPropertyName = i.value();
QString propertyValue = getStringPropertyFromMap(card, mtgjsonProperty);
if (!propertyValue.isEmpty()) {
printingInfo.setProperty(xmlPropertyName, propertyValue);
printingProps.insert(xmlPropertyName, propertyValue);
}
}
@ -304,7 +305,7 @@ int OracleImporter::importCardsFromSet(const CardSetPtr &currentSet, const QList
QString faceFlavorName = getStringPropertyFromMap(card, "faceFlavorName");
QString flavorName = !faceFlavorName.isEmpty() ? faceFlavorName : getStringPropertyFromMap(card, "flavorName");
if (!flavorName.isEmpty()) {
printingInfo.setProperty("flavorName", flavorName);
printingProps.insert("flavorName", flavorName);
}
// Identifiers
@ -313,10 +314,12 @@ int OracleImporter::importCardsFromSet(const CardSetPtr &currentSet, const QList
QString xmlPropertyName = i.value();
QString propertyValue = getStringPropertyFromMap(card.value("identifiers").toMap(), mtgjsonProperty);
if (!propertyValue.isEmpty()) {
printingInfo.setProperty(xmlPropertyName, propertyValue);
printingProps.insert(xmlPropertyName, propertyValue);
}
}
printingInfo.setProperties(printingProps);
QString numComponent;
const QString numProperty = printingInfo.getProperty("num");
const QChar lastChar = numProperty.isEmpty() ? QChar() : numProperty.back();