[Bazaar] Improve Bazaar Search Performance (#4615)

* Create Alternate BazaarSearch Routine

Establishes an alterative to the in memory bazaar search routine and instead uses a db query process.  For large Bazaars (with 1000s of items) this is much faster.  Testing with 30k items produced a search in ~1sec version 2.7sec for the in memory version.
Default is false - Do not use this version.

* Indexes for trader and items

* Set query-based bazaar search the default

* Update database_update_manifest.cpp

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
This commit is contained in:
Mitch Freeman
2025-01-26 21:04:56 -04:00
committed by GitHub
parent 119151c0e3
commit 7a226ca4ef
8 changed files with 388 additions and 601 deletions
-1
View File
@@ -339,7 +339,6 @@ public:
const char *message9 = nullptr);
void Tell_StringID(uint32 string_id, const char *who, const char *message);
void SendColoredText(uint32 color, std::string message);
void SendBazaarResults(uint32 trader_id, uint32 in_class, uint32 in_race, uint32 item_stat, uint32 item_slot, uint32 item_type, char item_name[64], uint32 min_price, uint32 max_price);
void SendTraderItem(uint32 item_id,uint16 quantity, TraderRepository::Trader &trader);
void DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria);
uint16 FindTraderItem(int32 SerialNumber,uint16 Quantity);
+7 -312
View File
@@ -1800,7 +1800,13 @@ void Client::SendBarterWelcome()
void Client::DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria)
{
auto results = Bazaar::GetSearchResults(database, search_criteria, GetZoneID(), GetInstanceID());
std::vector<BazaarSearchResultsFromDB_Struct> results = Bazaar::GetSearchResults(
database,
content_db,
search_criteria,
GetZoneID(),
GetInstanceID()
);
if (results.empty()) {
SendBazaarDone(GetID());
return;
@@ -1822,317 +1828,6 @@ void Client::DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria)
SendBazaarDeliveryCosts();
}
void Client::SendBazaarResults(
uint32 trader_id,
uint32 in_class,
uint32 in_race,
uint32 item_stat,
uint32 item_slot,
uint32 item_type,
char item_name[64],
uint32 min_price,
uint32 max_price
)
{
std::string search_values = " COUNT(item_id), trader.*, items.name ";
std::string search_criteria = " WHERE trader.item_id = items.id ";
if (trader_id > 0) {
Client *trader = entity_list.GetClientByID(trader_id);
if (trader) {
search_criteria.append(StringFormat(" AND trader.char_id = %i", trader->CharacterID()));
}
}
if (min_price != 0) {
search_criteria.append(StringFormat(" AND trader.item_cost >= %i", min_price));
}
if (max_price != 0) {
search_criteria.append(StringFormat(" AND trader.item_cost <= %i", max_price));
}
if (strlen(item_name) > 0) {
char *safeName = RemoveApostrophes(item_name);
search_criteria.append(StringFormat(" AND items.name LIKE '%%%s%%'", safeName));
safe_delete_array(safeName);
}
if (in_class != 0xFFFFFFFF) {
search_criteria.append(StringFormat(" AND MID(REVERSE(BIN(items.classes)), %i, 1) = 1", in_class));
}
if (in_race != 0xFFFFFFFF) {
search_criteria.append(StringFormat(" AND MID(REVERSE(BIN(items.races)), %i, 1) = 1", in_race));
}
if (item_slot != 0xFFFFFFFF) {
search_criteria.append(StringFormat(" AND MID(REVERSE(BIN(items.slots)), %i, 1) = 1", item_slot + 1));
}
switch (item_type) {
case 0xFFFFFFFF:
break;
case 0:
// 1H Slashing
search_criteria.append(" AND items.itemtype = 0 AND damage > 0");
break;
case 31:
search_criteria.append(" AND items.itemclass = 2");
break;
case 46:
search_criteria.append(" AND items.scrolleffect > 0 AND items.scrolleffect < 65000");
break;
case 47:
search_criteria.append(" AND items.worneffect = 998");
break;
case 48:
search_criteria.append(" AND items.worneffect >= 1298 AND items.worneffect <= 1307");
break;
case 49:
search_criteria.append(" AND items.focuseffect > 0");
break;
default:
search_criteria.append(StringFormat(" AND items.itemtype = %i", item_type));
}
switch (item_stat) {
case STAT_AC:
search_criteria.append(" AND items.ac > 0");
search_values.append(", items.ac");
break;
case STAT_AGI:
search_criteria.append(" AND items.aagi > 0");
search_values.append(", items.aagi");
break;
case STAT_CHA:
search_criteria.append(" AND items.acha > 0");
search_values.append(", items.acha");
break;
case STAT_DEX:
search_criteria.append(" AND items.adex > 0");
search_values.append(", items.adex");
break;
case STAT_INT:
search_criteria.append(" AND items.aint > 0");
search_values.append(", items.aint");
break;
case STAT_STA:
search_criteria.append(" AND items.asta > 0");
search_values.append(", items.asta");
break;
case STAT_STR:
search_criteria.append(" AND items.astr > 0");
search_values.append(", items.astr");
break;
case STAT_WIS:
search_criteria.append(" AND items.awis > 0");
search_values.append(", items.awis");
break;
case STAT_COLD:
search_criteria.append(" AND items.cr > 0");
search_values.append(", items.cr");
break;
case STAT_DISEASE:
search_criteria.append(" AND items.dr > 0");
search_values.append(", items.dr");
break;
case STAT_FIRE:
search_criteria.append(" AND items.fr > 0");
search_values.append(", items.fr");
break;
case STAT_MAGIC:
search_criteria.append(" AND items.mr > 0");
search_values.append(", items.mr");
break;
case STAT_POISON:
search_criteria.append(" AND items.pr > 0");
search_values.append(", items.pr");
break;
case STAT_HP:
search_criteria.append(" AND items.hp > 0");
search_values.append(", items.hp");
break;
case STAT_MANA:
search_criteria.append(" AND items.mana > 0");
search_values.append(", items.mana");
break;
case STAT_ENDURANCE:
search_criteria.append(" AND items.endur > 0");
search_values.append(", items.endur");
break;
case STAT_ATTACK:
search_criteria.append(" AND items.attack > 0");
search_values.append(", items.attack");
break;
case STAT_HP_REGEN:
search_criteria.append(" AND items.regen > 0");
search_values.append(", items.regen");
break;
case STAT_MANA_REGEN:
search_criteria.append(" AND items.manaregen > 0");
search_values.append(", items.manaregen");
break;
case STAT_HASTE:
search_criteria.append(" AND items.haste > 0");
search_values.append(", items.haste");
break;
case STAT_DAMAGE_SHIELD:
search_criteria.append(" AND items.damageshield > 0");
search_values.append(", items.damageshield");
break;
default:
search_values.append(", 0");
break;
}
std::string query = StringFormat(
"SELECT %s, SUM(charges), items.stackable "
"FROM trader, items %s GROUP BY items.id, charges, char_id LIMIT %i",
search_values.c_str(),
search_criteria.c_str(),
RuleI(Bazaar, MaxSearchResults)
);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
return;
}
LogTrading("SRCH: [{}]", query.c_str());
int Size = 0;
uint32 ID = 0;
if (results.RowCount() == static_cast<unsigned long>(RuleI(Bazaar, MaxSearchResults))) {
Message(
Chat::Yellow,
"Your search reached the limit of %i results. Please narrow your search down by selecting more options.",
RuleI(Bazaar, MaxSearchResults));
}
if (results.RowCount() == 0) {
auto outapp2 = new EQApplicationPacket(OP_BazaarSearch, sizeof(BazaarReturnDone_Struct));
BazaarReturnDone_Struct *brds = (BazaarReturnDone_Struct *) outapp2->pBuffer;
brds->TraderID = ID;
brds->Type = BazaarSearchDone;
brds->Unknown008 = 0xFFFFFFFF;
brds->Unknown012 = 0xFFFFFFFF;
brds->Unknown016 = 0xFFFFFFFF;
QueuePacket(outapp2);
safe_delete(outapp2);
return;
}
Size = results.RowCount() * sizeof(BazaarSearchResults_Struct);
auto buffer = new uchar[Size];
uchar *bufptr = buffer;
memset(buffer, 0, Size);
int Action = BazaarSearchResults;
uint32 Cost = 0;
int32 SerialNumber = 0;
char temp_buffer[64] = {0};
int Count = 0;
uint32 StatValue = 0;
for (auto &row = results.begin(); row != results.end(); ++row) {
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, Action);
Count = Strings::ToInt(row[0]);
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, Count);
SerialNumber = Strings::ToInt(row[3]);
VARSTRUCT_ENCODE_TYPE(int32, bufptr, SerialNumber);
Client *Trader2 = entity_list.GetClientByCharID(Strings::ToInt(row[1]));
if (Trader2) {
ID = Trader2->GetID();
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, ID);
}
else {
LogTrading("Unable to find trader: [{}]\n", Strings::ToInt(row[1]));
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, 0);
}
Cost = Strings::ToInt(row[5]);
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, Cost);
StatValue = Strings::ToInt(row[8]);
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, StatValue);
bool Stackable = Strings::ToInt(row[10]);
if (Stackable) {
int Charges = Strings::ToInt(row[9]);
sprintf(temp_buffer, "%s(%i)", row[7], Charges);
}
else {
sprintf(temp_buffer, "%s(%i)", row[7], Count);
}
memcpy(bufptr, &temp_buffer, strlen(temp_buffer));
bufptr += 64;
// Extra fields for SoD+
//
if (Trader2) {
sprintf(temp_buffer, "%s", Trader2->GetName());
}
else {
sprintf(temp_buffer, "Unknown");
}
memcpy(bufptr, &temp_buffer, strlen(temp_buffer));
bufptr += 64;
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, Strings::ToInt(row[1])); // ItemID
}
auto outapp = new EQApplicationPacket(OP_BazaarSearch, Size);
memcpy(outapp->pBuffer, buffer, Size);
QueuePacket(outapp);
safe_delete(outapp);
safe_delete_array(buffer);
auto outapp2 = new EQApplicationPacket(OP_BazaarSearch, sizeof(BazaarReturnDone_Struct));
BazaarReturnDone_Struct *brds = (BazaarReturnDone_Struct *) outapp2->pBuffer;
brds->TraderID = ID;
brds->Type = BazaarSearchDone;
brds->Unknown008 = 0xFFFFFFFF;
brds->Unknown012 = 0xFFFFFFFF;
brds->Unknown016 = 0xFFFFFFFF;
QueuePacket(outapp2);
safe_delete(outapp2);
}
static void UpdateTraderCustomerItemsAdded(
uint32 customer_id,
std::vector<BaseTraderRepository::Trader> trader_items,