mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-08 11:03:25 -08:00
[CardDB] Avoid copying printing maps and property strings (#7605)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [CardDB] Avoid copying printing maps and property strings Reading a card's sets was a measured hotspot: `SetToPrintingsMap setMap = card->getSets();` copies the whole QMap, and then iterating it with a non-const iterator detaches and deep-copies every PrintingInfo, including its property hash, on first use. A micro-benchmark of that exact pattern (copy plus non-const iteration) came out about 19x slower than a const reference over the same data. The querier, the cache writer, the XML writer, the picture loader and the printing/set selection widgets now take `getSets()` by const reference and only use const iterators. The tests that iterate generated data are covered by the existing card database and picture loader suites. String accessors were also returning by value where the value lives in a hash that has already been materialized: - CardInfo::getProperty() and the legacy getCardType()/getColors()/ getManaCost()/getCmc()/getPowTough()/getLoyalty() accessors, - PrintingInfo::getProperty(), getUuid(), getFlavorName(), getArtist(), and getSet(), - ExactCard::getPrinting() and getCardPtr(), - CardSet::getShortName(), getLongName() and getSetType(). getPrinting() in particular copied a PrintingInfo on every call, and a copy of an unmaterialized PrintingInfo has to take the properties mutex. Callers that need an owned copy still get one; the rest just read. Other allocation removals in the same path: - LazyPropertiesHash::ensureMaterialized() checks an atomic flag before taking the mutex, so property reads after the first one do not lock. The flag is release-stored after materialization and acquire-loaded by readers/copies, preserving the existing locking for the first read. - CardSet::getCorrectedShortName() no longer rebuilds its QSet of reserved Windows file names on every call. - CardSet::getIsKnownIgnored() no longer formats the release date into a throwaway string just to check its length. - The querier builds its card-type/format count caches without `cards.values()` or a properties key list, reserves its result lists, and picks a random card without materializing `cards.keys()`. - The querier's pinned-printing path reuses the CardInfoPtr it already has instead of looking the card up again by name. - CardDatabaseCache/XML serialization copy the alt-name QSet by reference. - The picture loader compiles its !prop!/!set! regular expressions once instead of per URL template, and walks the transform map with an iterator instead of keys() plus operator[]. Full build and ctest (628 tests) pass. * [CardDB] Address review comments PrintingInfo is a value type: it is stored in QLists and returned by value from the querier, so a const-ref getter can hand out a reference into a temporary. Return the QStrings and the CardSetPtr by value again. The constFind lookup stays, so a property read still never materializes the lazy property hash - only the last shared-data refcount is paid. * [CardDB] Undo the const-ref QString return changes QString is implicitly shared, so returning by value costs only a refcount and avoids handing out references into a lazily materialized hash. The constFind lookups stay, so a property read still never materializes the hash. * [CardDB] Use QMap::asKeyValueRange() in transformUrl The range is a view over the map's own iterators and yields references to the stored key and value, so it keeps the no-allocation property of the manual const iteration. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
b85436461e
commit
c25a2c5c3b
17 changed files with 108 additions and 79 deletions
|
|
@ -21,6 +21,7 @@
|
||||||
#include <libcockatrice/interfaces/noop_card_set_priority_controller.h>
|
#include <libcockatrice/interfaces/noop_card_set_priority_controller.h>
|
||||||
#include <libcockatrice/settings/cards_display_settings.h>
|
#include <libcockatrice/settings/cards_display_settings.h>
|
||||||
#include <libcockatrice/settings/download_settings.h>
|
#include <libcockatrice/settings/download_settings.h>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
CardPictureToLoad::CardPictureToLoad(const ExactCard &_card)
|
CardPictureToLoad::CardPictureToLoad(const ExactCard &_card)
|
||||||
: card(_card), urlTemplates(SettingsCache::instance().downloads().getAllURLs())
|
: card(_card), urlTemplates(SettingsCache::instance().downloads().getAllURLs())
|
||||||
|
|
@ -74,19 +75,20 @@ QList<CardSetPtr> CardPictureToLoad::extractSetsSorted(const ExactCard &card)
|
||||||
//! \todo Check if going through all sets is still necessary after the ExactCard refactor.
|
//! \todo Check if going through all sets is still necessary after the ExactCard refactor.
|
||||||
static PrintingInfo findPrintingForSet(const ExactCard &card, const QString &setName)
|
static PrintingInfo findPrintingForSet(const ExactCard &card, const QString &setName)
|
||||||
{
|
{
|
||||||
SetToPrintingsMap setsToPrintings = card.getInfo().getSets();
|
const auto &setsToPrintings = card.getInfo().getSets();
|
||||||
|
|
||||||
if (!setsToPrintings.contains(setName)) {
|
const auto setPrintings = setsToPrintings.constFind(setName);
|
||||||
|
if (setPrintings == setsToPrintings.constEnd() || setPrintings.value().isEmpty()) {
|
||||||
return PrintingInfo();
|
return PrintingInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &printing : setsToPrintings[setName]) {
|
for (const auto &printing : setPrintings.value()) {
|
||||||
if (printing.getUuid() == card.getPrinting().getUuid()) {
|
if (printing.getUuid() == card.getPrinting().getUuid()) {
|
||||||
return printing;
|
return printing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return setsToPrintings[setName][0];
|
return setPrintings.value().first();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CardPictureToLoad::populateSetUrls()
|
void CardPictureToLoad::populateSetUrls()
|
||||||
|
|
@ -183,10 +185,14 @@ static int parse(const QString &urlTemplate,
|
||||||
{
|
{
|
||||||
static const QRegularExpression rxFillWith("^(.+)_fill_with_(.+)$");
|
static const QRegularExpression rxFillWith("^(.+)_fill_with_(.+)$");
|
||||||
static const QRegularExpression rxSubStr("^(.+)_substr_(\\d+)_(\\d+)$");
|
static const QRegularExpression rxSubStr("^(.+)_substr_(\\d+)_(\\d+)$");
|
||||||
|
static const QRegularExpression rxCardProp("!prop:([^!]+)!");
|
||||||
|
static const QRegularExpression rxCardSet("!set:([^!]+)!");
|
||||||
|
|
||||||
const QRegularExpression rxCardProp("!" + propType + ":([^!]+)!");
|
// The property type only takes two values, so keep the expression that was
|
||||||
|
// compiled for it instead of rebuilding one per URL template.
|
||||||
|
const QRegularExpression &rxCardPropSelector = propType == "prop" ? rxCardProp : rxCardSet;
|
||||||
|
|
||||||
auto matches = rxCardProp.globalMatch(urlTemplate);
|
auto matches = rxCardPropSelector.globalMatch(urlTemplate);
|
||||||
while (matches.hasNext()) {
|
while (matches.hasNext()) {
|
||||||
auto match = matches.next();
|
auto match = matches.next();
|
||||||
QString templatePropertyName = match.captured(1);
|
QString templatePropertyName = match.captured(1);
|
||||||
|
|
@ -303,10 +309,10 @@ QString CardPictureToLoad::transformUrl(const QString &urlTemplate) const
|
||||||
}
|
}
|
||||||
|
|
||||||
QString transformedUrl = urlTemplate;
|
QString transformedUrl = urlTemplate;
|
||||||
for (const QString &prop : transformMap.keys()) {
|
for (const auto &[prop, value] : std::as_const(transformMap).asKeyValueRange()) {
|
||||||
if (transformedUrl.contains(prop)) {
|
if (transformedUrl.contains(prop)) {
|
||||||
if (!transformMap[prop].isEmpty()) {
|
if (!value.isEmpty()) {
|
||||||
transformedUrl.replace(prop, QUrl::toPercentEncoding(transformMap[prop]));
|
transformedUrl.replace(prop, QUrl::toPercentEncoding(value));
|
||||||
} else {
|
} else {
|
||||||
/* This means the template is requesting information that is not
|
/* This means the template is requesting information that is not
|
||||||
* populated in this card, so it should return an empty string,
|
* populated in this card, so it should return an empty string,
|
||||||
|
|
|
||||||
|
|
@ -274,9 +274,9 @@ QMap<QString, int> DlgSelectSetForCards::getSetsForCards()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetToPrintingsMap setMap = infoPtr->getSets();
|
const auto &setMap = infoPtr->getSets();
|
||||||
for (auto &setName : setMap.keys()) {
|
for (auto it = setMap.constBegin(); it != setMap.constEnd(); ++it) {
|
||||||
setCounts[setName]++;
|
setCounts[it.key()]++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -393,8 +393,8 @@ QMap<QString, QStringList> DlgSelectSetForCards::getCardsForSets()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetToPrintingsMap setMap = infoPtr->getSets();
|
const auto &setMap = infoPtr->getSets();
|
||||||
for (auto it = setMap.begin(); it != setMap.end(); ++it) {
|
for (auto it = setMap.constBegin(); it != setMap.constEnd(); ++it) {
|
||||||
setCards[it.key()].append(cardName);
|
setCards[it.key()].append(cardName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@ void PrintingSelector::getAllSetsForCurrentCard()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetToPrintingsMap setMap = selectedCard->getSets();
|
const auto &setMap = selectedCard->getSets();
|
||||||
const QList<PrintingInfo> sortedPrintings = sortToolBar->sortSets(setMap);
|
const QList<PrintingInfo> sortedPrintings = sortToolBar->sortSets(setMap);
|
||||||
const QList<PrintingInfo> filteredPrintings =
|
const QList<PrintingInfo> filteredPrintings =
|
||||||
sortToolBar->filterSets(sortedPrintings, searchBar->getSearchText().trimmed().toLower());
|
sortToolBar->filterSets(sortedPrintings, searchBar->getSearchText().trimmed().toLower());
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ QList<PrintingInfo> PrintingSelectorCardSortingWidget::prependPrintingsInDeck(co
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
SetToPrintingsMap setMap = selectedCard->getSets();
|
const auto &setMap = selectedCard->getSets();
|
||||||
QList<QPair<PrintingInfo, int>> countList;
|
QList<QPair<PrintingInfo, int>> countList;
|
||||||
|
|
||||||
// Collect sets with their counts
|
// Collect sets with their counts
|
||||||
|
|
|
||||||
|
|
@ -432,10 +432,11 @@ void VisualDatabaseDisplayWidget::loadPage(int start, int end)
|
||||||
|
|
||||||
if (CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(name.toString())) {
|
if (CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(name.toString())) {
|
||||||
if (!setFilters.empty()) {
|
if (!setFilters.empty()) {
|
||||||
SetToPrintingsMap setMap = info->getSets();
|
const auto &setMap = info->getSets();
|
||||||
for (const CardFilter *setFilter : setFilters) {
|
for (const CardFilter *setFilter : setFilters) {
|
||||||
if (setMap.contains(setFilter->term())) {
|
const auto it = setMap.constFind(setFilter->term());
|
||||||
for (PrintingInfo printing : setMap[setFilter->term()]) {
|
if (it != setMap.constEnd()) {
|
||||||
|
for (const PrintingInfo &printing : it.value()) {
|
||||||
addCardToDisplay(ExactCard(info, printing));
|
addCardToDisplay(ExactCard(info, printing));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ QString CardInfo::simplifyName(const QString &name)
|
||||||
|
|
||||||
QChar CardInfo::getColorChar() const
|
QChar CardInfo::getColorChar() const
|
||||||
{
|
{
|
||||||
QString colors = getColors();
|
const QString colors = getColors();
|
||||||
switch (colors.size()) {
|
switch (colors.size()) {
|
||||||
case 0:
|
case 0:
|
||||||
return QChar();
|
return QChar();
|
||||||
|
|
|
||||||
|
|
@ -280,7 +280,7 @@ public:
|
||||||
{
|
{
|
||||||
return simpleName;
|
return simpleName;
|
||||||
}
|
}
|
||||||
const QSet<QString> &getAltNames()
|
const QSet<QString> &getAltNames() const
|
||||||
{
|
{
|
||||||
return altNames;
|
return altNames;
|
||||||
}
|
}
|
||||||
|
|
@ -395,7 +395,9 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] QString getProperty(const QString &propertyName) const
|
[[nodiscard]] QString getProperty(const QString &propertyName) const
|
||||||
{
|
{
|
||||||
return getPropertiesHash().value(propertyName);
|
const auto &propertiesHash = getPropertiesHash();
|
||||||
|
const auto it = propertiesHash.constFind(propertyName);
|
||||||
|
return it != propertiesHash.constEnd() ? it.value() : QString();
|
||||||
}
|
}
|
||||||
void setProperty(const QString &_name, const QString &_value);
|
void setProperty(const QString &_name, const QString &_value);
|
||||||
[[nodiscard]] bool hasProperty(const QString &propertyName) const
|
[[nodiscard]] bool hasProperty(const QString &propertyName) const
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ void writeCard(QDataStream &out, const CardInfoPtr &card)
|
||||||
// per-printing alt-name scan entirely.
|
// per-printing alt-name scan entirely.
|
||||||
writeString(out, card->getSimpleName());
|
writeString(out, card->getSimpleName());
|
||||||
{
|
{
|
||||||
const QSet<QString> altNames = card->getAltNames();
|
const QSet<QString> &altNames = card->getAltNames();
|
||||||
out << static_cast<quint32>(altNames.size());
|
out << static_cast<quint32>(altNames.size());
|
||||||
for (const QString &alt : altNames) {
|
for (const QString &alt : altNames) {
|
||||||
writeString(out, alt);
|
writeString(out, alt);
|
||||||
|
|
@ -218,7 +218,7 @@ void writeCard(QDataStream &out, const CardInfoPtr &card)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setsToPrintings
|
// setsToPrintings
|
||||||
const SetToPrintingsMap sets = card->getSets();
|
const auto &sets = card->getSets();
|
||||||
out << static_cast<quint32>(sets.size());
|
out << static_cast<quint32>(sets.size());
|
||||||
for (auto it = sets.constBegin(); it != sets.constEnd(); ++it) {
|
for (auto it = sets.constBegin(); it != sets.constEnd(); ++it) {
|
||||||
writeString(out, it.key());
|
writeString(out, it.key());
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include <QRandomGenerator>
|
#include <QRandomGenerator>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QStringLiteral>
|
#include <QStringLiteral>
|
||||||
|
#include <iterator>
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
|
|
||||||
CardDatabaseQuerier::CardDatabaseQuerier(QObject *_parent,
|
CardDatabaseQuerier::CardDatabaseQuerier(QObject *_parent,
|
||||||
|
|
@ -52,6 +53,7 @@ CardInfoPtr CardDatabaseQuerier::getCardInfo(const QString &cardName) const
|
||||||
QList<CardInfoPtr> CardDatabaseQuerier::getCardInfos(const QStringList &cardNames) const
|
QList<CardInfoPtr> CardDatabaseQuerier::getCardInfos(const QStringList &cardNames) const
|
||||||
{
|
{
|
||||||
QList<CardInfoPtr> cardInfos;
|
QList<CardInfoPtr> cardInfos;
|
||||||
|
cardInfos.reserve(cardNames.size());
|
||||||
for (const QString &cardName : cardNames) {
|
for (const QString &cardName : cardNames) {
|
||||||
CardInfoPtr ptr = db->cards.value(cardName);
|
CardInfoPtr ptr = db->cards.value(cardName);
|
||||||
if (ptr) {
|
if (ptr) {
|
||||||
|
|
@ -90,6 +92,7 @@ CardInfoPtr CardDatabaseQuerier::lookupCardByName(const QString &name) const
|
||||||
QList<ExactCard> CardDatabaseQuerier::getCards(const QList<CardRef> &cardRefs) const
|
QList<ExactCard> CardDatabaseQuerier::getCards(const QList<CardRef> &cardRefs) const
|
||||||
{
|
{
|
||||||
QList<ExactCard> cards;
|
QList<ExactCard> cards;
|
||||||
|
cards.reserve(cardRefs.size());
|
||||||
for (const auto &cardRef : cardRefs) {
|
for (const auto &cardRef : cardRefs) {
|
||||||
ExactCard card = getCard(cardRef);
|
ExactCard card = getCard(cardRef);
|
||||||
if (card) {
|
if (card) {
|
||||||
|
|
@ -149,10 +152,10 @@ ExactCard CardDatabaseQuerier::getRandomCard() const
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto keys = db->cards.keys();
|
const auto randomIndex = QRandomGenerator::global()->bounded(db->cards.size());
|
||||||
int randomIndex = QRandomGenerator::global()->bounded(keys.size());
|
auto it = db->cards.constBegin();
|
||||||
const QString &randomKey = keys.at(randomIndex);
|
std::advance(it, randomIndex);
|
||||||
CardInfoPtr randomCard = getCardInfo(randomKey);
|
CardInfoPtr randomCard = it.value();
|
||||||
|
|
||||||
return ExactCard{randomCard, getPreferredPrinting(randomCard)};
|
return ExactCard{randomCard, getPreferredPrinting(randomCard)};
|
||||||
}
|
}
|
||||||
|
|
@ -215,20 +218,23 @@ PrintingInfo CardDatabaseQuerier::getSpecificPrinting(const QString &cardName,
|
||||||
return PrintingInfo(nullptr);
|
return PrintingInfo(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetToPrintingsMap setMap = cardInfo->getSets();
|
const auto &setMap = cardInfo->getSets();
|
||||||
if (setMap.empty()) {
|
if (setMap.empty()) {
|
||||||
return PrintingInfo(nullptr);
|
return PrintingInfo(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &printings : setMap) {
|
for (const auto &printings : setMap) {
|
||||||
for (auto &cardInfoForSet : printings) {
|
for (const auto &cardInfoForSet : printings) {
|
||||||
|
const CardSetPtr &set = cardInfoForSet.getSet();
|
||||||
|
if (!set) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!collectorNumber.isEmpty()) {
|
if (!collectorNumber.isEmpty()) {
|
||||||
if (cardInfoForSet.getSet()->getShortName() == setShortName &&
|
if (set->getShortName() == setShortName && cardInfoForSet.getProperty("num") == collectorNumber) {
|
||||||
cardInfoForSet.getProperty("num") == collectorNumber) {
|
|
||||||
return cardInfoForSet;
|
return cardInfoForSet;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (cardInfoForSet.getSet()->getShortName() == setShortName) {
|
if (set->getShortName() == setShortName) {
|
||||||
return cardInfoForSet;
|
return cardInfoForSet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -284,10 +290,10 @@ PrintingInfo CardDatabaseQuerier::getPreferredPrinting(const CardInfoPtr &cardIn
|
||||||
const auto &pinnedPrintingProviderId = prefs->getCardPreferenceOverride(cardInfo->getName());
|
const auto &pinnedPrintingProviderId = prefs->getCardPreferenceOverride(cardInfo->getName());
|
||||||
|
|
||||||
if (!pinnedPrintingProviderId.isEmpty()) {
|
if (!pinnedPrintingProviderId.isEmpty()) {
|
||||||
return getSpecificPrinting({cardInfo->getName(), pinnedPrintingProviderId});
|
return findPrintingWithId(cardInfo, pinnedPrintingProviderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetToPrintingsMap setMap = cardInfo->getSets();
|
const auto &setMap = cardInfo->getSets();
|
||||||
if (setMap.empty()) {
|
if (setMap.empty()) {
|
||||||
return PrintingInfo(nullptr);
|
return PrintingInfo(nullptr);
|
||||||
}
|
}
|
||||||
|
|
@ -297,8 +303,8 @@ PrintingInfo CardDatabaseQuerier::getPreferredPrinting(const CardInfoPtr &cardIn
|
||||||
SetPriorityComparator comparator;
|
SetPriorityComparator comparator;
|
||||||
|
|
||||||
for (const auto &printings : setMap) {
|
for (const auto &printings : setMap) {
|
||||||
for (auto &printing : printings) {
|
for (const auto &printing : printings) {
|
||||||
CardSetPtr currentSet = printing.getSet();
|
const CardSetPtr ¤tSet = printing.getSet();
|
||||||
if (!preferredSet || comparator(currentSet, preferredSet)) {
|
if (!preferredSet || comparator(currentSet, preferredSet)) {
|
||||||
preferredSet = currentSet;
|
preferredSet = currentSet;
|
||||||
preferredPrinting = printing;
|
preferredPrinting = printing;
|
||||||
|
|
@ -315,17 +321,18 @@ PrintingInfo CardDatabaseQuerier::getPreferredPrinting(const CardInfoPtr &cardIn
|
||||||
|
|
||||||
QString CardDatabaseQuerier::getPreferredPrintingProviderId(const QString &cardName) const
|
QString CardDatabaseQuerier::getPreferredPrintingProviderId(const QString &cardName) const
|
||||||
{
|
{
|
||||||
PrintingInfo preferredPrinting = getPreferredPrinting(cardName);
|
CardInfoPtr cardInfo = getCardInfo(cardName);
|
||||||
QString uuid = preferredPrinting.getUuid();
|
if (!cardInfo) {
|
||||||
|
return cardName;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintingInfo preferredPrinting = getPreferredPrinting(cardInfo);
|
||||||
|
const QString &uuid = preferredPrinting.getUuid();
|
||||||
if (!uuid.isEmpty()) {
|
if (!uuid.isEmpty()) {
|
||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
CardInfoPtr defaultCardInfo = getCardInfo(cardName);
|
return cardInfo->getName();
|
||||||
if (defaultCardInfo.isNull()) {
|
|
||||||
return cardName;
|
|
||||||
}
|
|
||||||
return defaultCardInfo->getName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList CardDatabaseQuerier::getAllMainCardTypes() const
|
QStringList CardDatabaseQuerier::getAllMainCardTypes() const
|
||||||
|
|
@ -339,8 +346,8 @@ QMap<QString, int> CardDatabaseQuerier::getAllMainCardTypesWithCount() const
|
||||||
// produces an empty map, so the cache is only ever empty when it needs a
|
// produces an empty map, so the cache is only ever empty when it needs a
|
||||||
// (trivially cheap) rebuild.
|
// (trivially cheap) rebuild.
|
||||||
if (mainCardTypeCountsCache.isEmpty()) {
|
if (mainCardTypeCountsCache.isEmpty()) {
|
||||||
for (const auto &card : db->cards.values()) {
|
for (const auto &card : db->cards) {
|
||||||
QString type = card->getMainCardType();
|
const QString type = card->getMainCardType();
|
||||||
mainCardTypeCountsCache[type]++;
|
mainCardTypeCountsCache[type]++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -351,13 +358,13 @@ QMap<QString, int> CardDatabaseQuerier::getAllMainCardTypesWithCount() const
|
||||||
QMap<QString, int> CardDatabaseQuerier::getAllSubCardTypesWithCount() const
|
QMap<QString, int> CardDatabaseQuerier::getAllSubCardTypesWithCount() const
|
||||||
{
|
{
|
||||||
if (subCardTypeCountsCache.isEmpty()) {
|
if (subCardTypeCountsCache.isEmpty()) {
|
||||||
for (const auto &card : db->cards.values()) {
|
for (const auto &card : db->cards) {
|
||||||
QString type = card->getCardType();
|
const QString type = card->getCardType();
|
||||||
|
|
||||||
QStringList parts = type.split(" — ");
|
const QStringList parts = type.split(" — ");
|
||||||
|
|
||||||
if (parts.size() > 1) { // Ensure there are subtypes
|
if (parts.size() > 1) { // Ensure there are subtypes
|
||||||
QStringList subtypes = parts[1].split(" ", Qt::SkipEmptyParts);
|
const QStringList subtypes = parts[1].split(" ", Qt::SkipEmptyParts);
|
||||||
|
|
||||||
for (const QString &subtype : subtypes) {
|
for (const QString &subtype : subtypes) {
|
||||||
subCardTypeCountsCache[subtype]++;
|
subCardTypeCountsCache[subtype]++;
|
||||||
|
|
@ -377,13 +384,13 @@ FormatRulesPtr CardDatabaseQuerier::getFormat(const QString &formatName) const
|
||||||
QMap<QString, int> CardDatabaseQuerier::getAllFormatsWithCount() const
|
QMap<QString, int> CardDatabaseQuerier::getAllFormatsWithCount() const
|
||||||
{
|
{
|
||||||
if (formatsCountCache.isEmpty()) {
|
if (formatsCountCache.isEmpty()) {
|
||||||
for (const auto &card : db->cards.values()) {
|
static const QString formatPrefix = QStringLiteral("format-");
|
||||||
QStringList allProps = card->getProperties();
|
|
||||||
|
|
||||||
for (const QString &prop : allProps) {
|
for (const auto &card : db->cards) {
|
||||||
if (prop.startsWith("format-")) {
|
const auto &properties = card->getPropertiesHash();
|
||||||
QString formatName = prop.mid(QStringLiteral("format-").size());
|
for (auto it = properties.constBegin(); it != properties.constEnd(); ++it) {
|
||||||
formatsCountCache[formatName]++;
|
if (it.key().startsWith(formatPrefix)) {
|
||||||
|
formatsCountCache[it.key().mid(formatPrefix.size())]++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -401,7 +401,7 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
|
||||||
}
|
}
|
||||||
|
|
||||||
// sets
|
// sets
|
||||||
const SetToPrintingsMap setMap = info->getSets();
|
const auto &setMap = info->getSets();
|
||||||
for (const auto &printings : setMap) {
|
for (const auto &printings : setMap) {
|
||||||
for (const PrintingInfo &set : printings) {
|
for (const PrintingInfo &set : printings) {
|
||||||
xml.writeStartElement("set");
|
xml.writeStartElement("set");
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,15 @@ LazyPropertiesHash::LazyPropertiesHash(const QHash<QString, QString> &properties
|
||||||
LazyPropertiesHash::LazyPropertiesHash(const LazyPropertiesHash &other)
|
LazyPropertiesHash::LazyPropertiesHash(const LazyPropertiesHash &other)
|
||||||
{
|
{
|
||||||
// since we do not allow dematerialization, we only need to lock if not materialized yet
|
// since we do not allow dematerialization, we only need to lock if not materialized yet
|
||||||
if (other.isMaterialized) {
|
if (other.isMaterialized.load(std::memory_order_acquire)) {
|
||||||
blob = other.blob;
|
blob = other.blob;
|
||||||
properties = other.properties;
|
properties = other.properties;
|
||||||
isMaterialized = true;
|
isMaterialized.store(true, std::memory_order_relaxed);
|
||||||
} else {
|
} else {
|
||||||
QMutexLocker lock(&other.propertiesMutex);
|
QMutexLocker lock(&other.propertiesMutex);
|
||||||
blob = other.blob;
|
blob = other.blob;
|
||||||
properties = other.properties;
|
properties = other.properties;
|
||||||
isMaterialized = false;
|
isMaterialized.store(other.isMaterialized.load(std::memory_order_relaxed), std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,15 +39,15 @@ LazyPropertiesHash &LazyPropertiesHash::operator=(const LazyPropertiesHash &othe
|
||||||
}
|
}
|
||||||
|
|
||||||
// since we do not allow dematerialization, we only need to lock if not materialized yet
|
// since we do not allow dematerialization, we only need to lock if not materialized yet
|
||||||
if (other.isMaterialized) {
|
if (other.isMaterialized.load(std::memory_order_acquire)) {
|
||||||
blob = other.blob;
|
blob = other.blob;
|
||||||
properties = other.properties;
|
properties = other.properties;
|
||||||
isMaterialized = true;
|
isMaterialized.store(true, std::memory_order_relaxed);
|
||||||
} else {
|
} else {
|
||||||
QMutexLocker lock(&other.propertiesMutex);
|
QMutexLocker lock(&other.propertiesMutex);
|
||||||
blob = other.blob;
|
blob = other.blob;
|
||||||
properties = other.properties;
|
properties = other.properties;
|
||||||
isMaterialized = false;
|
isMaterialized.store(false, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
|
|
@ -55,9 +55,15 @@ LazyPropertiesHash &LazyPropertiesHash::operator=(const LazyPropertiesHash &othe
|
||||||
|
|
||||||
void LazyPropertiesHash::ensureMaterialized() const
|
void LazyPropertiesHash::ensureMaterialized() const
|
||||||
{
|
{
|
||||||
|
// Materialization happens once and is never undone, so the common case can
|
||||||
|
// skip the mutex entirely once it has happened.
|
||||||
|
if (isMaterialized.load(std::memory_order_acquire)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QMutexLocker lock(&propertiesMutex);
|
QMutexLocker lock(&propertiesMutex);
|
||||||
|
|
||||||
if (isMaterialized) {
|
if (isMaterialized.load(std::memory_order_relaxed)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,7 +75,7 @@ void LazyPropertiesHash::ensureMaterialized() const
|
||||||
|
|
||||||
blob.clear();
|
blob.clear();
|
||||||
|
|
||||||
isMaterialized = true;
|
isMaterialized.store(true, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString LazyPropertiesHash::value(const QString &key) const
|
QString LazyPropertiesHash::value(const QString &key) const
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A property map that can lazily deserialize blobs to avoid loading overhead.
|
* @brief A property map that can lazily deserialize blobs to avoid loading overhead.
|
||||||
|
|
@ -18,10 +19,10 @@
|
||||||
class LazyPropertiesHash
|
class LazyPropertiesHash
|
||||||
{
|
{
|
||||||
|
|
||||||
mutable QByteArray blob; ///< Serialized properties (load form).
|
mutable QByteArray blob; ///< Serialized properties (load form).
|
||||||
mutable QHash<QString, QString> properties; ///< Materialized properties (query form).
|
mutable QHash<QString, QString> properties; ///< Materialized properties (query form).
|
||||||
mutable QMutex propertiesMutex; ///< Guards lazy materialization.
|
mutable QMutex propertiesMutex; ///< Guards lazy materialization.
|
||||||
mutable bool isMaterialized = false; ///< Whether propertiesCache is valid.
|
mutable std::atomic<bool> isMaterialized{false}; ///< Whether properties is valid.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Materializes properties from blob if not already done. Clears blob afterward.
|
* @brief Materializes properties from blob if not already done. Clears blob afterward.
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ public:
|
||||||
*
|
*
|
||||||
* May be null if the ExactCard is empty.
|
* May be null if the ExactCard is empty.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] CardInfoPtr getCardPtr() const
|
[[nodiscard]] const CardInfoPtr &getCardPtr() const
|
||||||
{
|
{
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +57,7 @@ public:
|
||||||
*
|
*
|
||||||
* May be empty if no specific printing was assigned.
|
* May be empty if no specific printing was assigned.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] PrintingInfo getPrinting() const
|
[[nodiscard]] const PrintingInfo &getPrinting() const
|
||||||
{
|
{
|
||||||
return printing;
|
return printing;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,10 @@ void PrintingInfo::setProperty(const QString &_name, const QString &_value)
|
||||||
*/
|
*/
|
||||||
QString PrintingInfo::getUuid() const
|
QString PrintingInfo::getUuid() const
|
||||||
{
|
{
|
||||||
return getPropertiesHash().value("uuid");
|
return getProperty("uuid");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PrintingInfo::getFlavorName() const
|
QString PrintingInfo::getFlavorName() const
|
||||||
{
|
{
|
||||||
return getPropertiesHash().value("flavorName");
|
return getProperty("flavorName");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,9 @@ public:
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] QString getProperty(const QString &propertyName) const
|
[[nodiscard]] QString getProperty(const QString &propertyName) const
|
||||||
{
|
{
|
||||||
return properties.value(propertyName);
|
const auto &propertiesHash = getPropertiesHash();
|
||||||
|
const auto it = propertiesHash.constFind(propertyName);
|
||||||
|
return it != propertiesHash.constEnd() ? it.value() : QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,14 @@ CardSetPtr CardSet::newInstance(ICardSetPriorityController *_priorityController,
|
||||||
QString CardSet::getCorrectedShortName() const
|
QString CardSet::getCorrectedShortName() const
|
||||||
{
|
{
|
||||||
// For Windows machines.
|
// For Windows machines.
|
||||||
QSet<QString> invalidFileNames;
|
static const QSet<QString> invalidFileNames = {
|
||||||
invalidFileNames << "CON" << "PRN" << "AUX" << "NUL" << "COM1" << "COM2" << "COM3" << "COM4" << "COM5" << "COM6"
|
QStringLiteral("CON"), QStringLiteral("PRN"), QStringLiteral("AUX"), QStringLiteral("NUL"),
|
||||||
<< "COM7" << "COM8" << "COM9" << "LPT1" << "LPT2" << "LPT3" << "LPT4" << "LPT5" << "LPT6" << "LPT7"
|
QStringLiteral("COM1"), QStringLiteral("COM2"), QStringLiteral("COM3"), QStringLiteral("COM4"),
|
||||||
<< "LPT8" << "LPT9";
|
QStringLiteral("COM5"), QStringLiteral("COM6"), QStringLiteral("COM7"), QStringLiteral("COM8"),
|
||||||
|
QStringLiteral("COM9"), QStringLiteral("LPT1"), QStringLiteral("LPT2"), QStringLiteral("LPT3"),
|
||||||
|
QStringLiteral("LPT4"), QStringLiteral("LPT5"), QStringLiteral("LPT6"), QStringLiteral("LPT7"),
|
||||||
|
QStringLiteral("LPT8"), QStringLiteral("LPT9"),
|
||||||
|
};
|
||||||
|
|
||||||
return invalidFileNames.contains(shortName) ? shortName + "_" : shortName;
|
return invalidFileNames.contains(shortName) ? shortName + "_" : shortName;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@ public:
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] bool getIsKnownIgnored() const
|
[[nodiscard]] bool getIsKnownIgnored() const
|
||||||
{
|
{
|
||||||
return longName.length() + setType.length() + releaseDate.toString().length() == 0;
|
return longName.isEmpty() && setType.isEmpty() && !releaseDate.isValid();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue