Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/game/AuctionHouseBot/BrowsePending.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ struct PendingBrowse
uint8 deferEluna;
uint32 listfrom;
std::wstring wname; ///< lower-cased UTF-16 (in-process Utf8FitTo)
std::vector<uint32> clientOutbidIds; ///< BIDDER client outbid ids (client order)
};

class BrowsePendingMap
Expand Down
17 changes: 0 additions & 17 deletions src/game/WorldHandlers/AuctionHouseHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1612,22 +1612,6 @@ static void AhBuildKnownRecipeCastSpells(Player* player, std::vector<uint32>& ou
}
}

// V8: re-resolve the AuctionHouseObject for a stored pending request, mirroring
// GetAuctionsMap by team. pb.house: 0=ALLIANCE,1=HORDE,2=NEUTRAL (== the
// AuctionHouseType values); allHouses => the neutral house. Non-static so the
// world thread's IPC_BROWSE_RESULT reply branch can use it for BIDDER prepends.
AuctionHouseObject* AhResolveHouse(const PendingBrowse& pb)
{
AuctionHouseType t = AUCTION_HOUSE_NEUTRAL;
if (pb.allHouses == 0u)
{
t = (pb.house == 0u) ? AUCTION_HOUSE_ALLIANCE
: (pb.house == 1u) ? AUCTION_HOUSE_HORDE
: AUCTION_HOUSE_NEUTRAL;
}
return sAuctionMgr.GetAuctionsMap(t);
}

// Coordinator model: the worker is the AH read authority. When it cannot serve
// (down, a transient register/encode/send failure, queue-full, oversize, or a
// reply timeout) mangosd serves NOTHING in-process. It sends an empty list so
Expand Down Expand Up @@ -1713,7 +1697,6 @@ void WorldSession::HandleAuctionListBidderItems(WorldPacket& recv_data)
pb.deferEluna = 0u;
pb.listfrom = listfrom;
pb.wname.clear();
pb.clientOutbidIds = clientOutbidIds;
pb.seq = sWorld.GetBrowsePending().NextSeqFor(pb.playerGuidLow, pb.kind);

BrowseQuery q;
Expand Down
63 changes: 3 additions & 60 deletions src/game/WorldHandlers/World.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,10 @@ INSTANTIATE_SINGLETON_1(World);

extern void LoadGameObjectModelList();

// SP-1 coordinator: the "AH unavailable" responder + house re-resolution (for
// the BIDDER outbid-prepend), defined in AuctionHouseHandler.cpp (next to the
// three async-proxy handlers). The world thread calls these from the
// IPC_BROWSE_RESULT reply branch and the TTL sweep.
// SP-1 coordinator: the "AH unavailable" responder, defined in
// AuctionHouseHandler.cpp next to the three async-proxy handlers. The world
// thread calls it from the IPC_BROWSE_RESULT reply branch and the TTL sweep.
void AhSendBrowseUnavailable(WorldSession* session, uint8 kind);
AuctionHouseObject* AhResolveHouse(const PendingBrowse& pb);

volatile bool World::m_stopEvent = false;
uint8 World::m_ExitCode = SHUTDOWN_EXIT_CODE;
Expand Down Expand Up @@ -1665,61 +1663,6 @@ void World::HandleAhInbound(const IpcMessage& msg)
opcode = SMSG_AUCTION_BIDDER_LIST_RESULT;
}

if (pb.kind == uint8(BROWSE_BIDDER))
{
// I1: BIDDER prepends the client-supplied outbid entries in CLIENT
// ORDER (mirroring HandleAuctionListBidderItems) before the worker
// bidder-sweep entries. The client outbid rows are resolved live
// from the player's house via GetAuction(id)->BuildAuctionInfo.
AuctionHouseObject* house = AhResolveHouse(pb);
ByteBuffer rows;
uint32 prependCount = 0;
if (house)
{
for (size_t i = 0; i < pb.clientOutbidIds.size(); ++i)
{
AuctionEntry* ae = house->GetAuction(pb.clientOutbidIds[i]);
if (!ae)
{
continue;
}
WorldPacket one(opcode, 60);
if (ae->BuildAuctionInfo(one))
{
rows.append(one.contents(), one.size());
++prependCount;
}
}
}
// Assemble: count(prepend+sweep) | client rows | sweep rows | total
WorldPacket data(opcode, 4 + rows.size() + finalEntries.size() * 60 + 4);
data << uint32(prependCount + uint32(finalEntries.size()));
if (rows.size())
{
data.append(rows.contents(), rows.size());
}
ByteBuffer sweepRows;
for (size_t i = 0; i < finalEntries.size(); ++i)
{
const BrowseEntry& e = finalEntries[i];
sweepRows << uint32(e.id) << uint32(e.itemEntry) << uint32(e.enchantId)
<< uint32(e.randomPropId) << uint32(e.suffixFactor)
<< uint32(e.count) << uint32(e.charges)
<< ObjectGuid(HIGHGUID_PLAYER, e.ownerGuidLow)
<< uint32(e.startbid) << uint32(e.outbid) << uint32(e.buyout)
<< uint32(e.timeLeftMs)
<< ObjectGuid(HIGHGUID_PLAYER, e.bidderGuidLow)
<< uint32(e.curBid);
}
if (sweepRows.size())
{
data.append(sweepRows.contents(), sweepRows.size());
}
data << uint32(totalcount + prependCount);
session->SendPacket(&data);
break;
}

WorldPacket data(opcode, 4 + 4 + finalEntries.size() * 60);
ByteBuffer assembled;
AhAssembleBrowseListBody(finalEntries, totalcount, assembled);
Expand Down
70 changes: 42 additions & 28 deletions src/mangosd/MangosdTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1193,40 +1193,54 @@ static int RunAhBrowsePendingTest()
return 1;
}

// SMSG assembly: count, first entry id, totalcount appear in order.
// SMSG assembly preserves the worker's complete bidder order, including a
// duplicate auction id, and writes the worker-provided totalcount.
{
std::vector<BrowseEntry> entries;
BrowseEntry e; e.id=42u; e.itemEntry=19019u; e.enchantId=0u; e.randomPropId=0u;
e.suffixFactor=0u; e.count=1u; e.charges=0; e.ownerGuidLow=4u; e.startbid=100u;
e.outbid=0u; e.buyout=5000u; e.timeLeftMs=720000u; e.bidderGuidLow=0u; e.curBid=100u;
entries.push_back(e);
const uint32 orderedIds[] = { 9u, 3u, 9u, 7u };
for (size_t i = 0; i < 4u; ++i)
{
BrowseEntry e;
e.id=orderedIds[i]; e.itemEntry=19019u; e.enchantId=0u;
e.randomPropId=0u; e.suffixFactor=0u; e.count=1u;
e.charges=0; e.ownerGuidLow=4u; e.startbid=100u;
e.outbid=0u; e.buyout=5000u; e.timeLeftMs=720000u;
e.bidderGuidLow=0u; e.curBid=100u;
entries.push_back(e);
}
ByteBuffer body;
AhAssembleBrowseListBody(entries, 3u, body);
AhAssembleBrowseListBody(entries, 4u, body);
uint32 c=0; body >> c;
if (c != 1u) { printf("ahbrowsepending FAIL: assembled count %u\n", unsigned(c)); return 1; }
uint32 firstId=0; body >> firstId;
if (firstId != 42u) { printf("ahbrowsepending FAIL: first id %u\n", unsigned(firstId)); return 1; }
// Verify all remaining BrowseEntry fields + trailing totalcount.
// AhAssembleBrowseListBody writes fields in BuildAuctionInfo wire order:
// itemEntry, enchantId, randomPropId, suffixFactor, count, charges (6 x uint32),
// ownerGuid (uint64), startbid, outbid, buyout, timeLeftMs (4 x uint32),
// bidderGuid (uint64), curBid (uint32), then trailing uint32 totalcount.
if (c != 4u)
{
printf("ahbrowsepending FAIL: assembled count %u\n",
unsigned(c));
return 1;
}
uint32 dummy32 = 0; uint64 dummy64 = 0;
body >> dummy32; // itemEntry
body >> dummy32; // enchantId
body >> dummy32; // randomPropId
body >> dummy32; // suffixFactor
body >> dummy32; // count
body >> dummy32; // charges
body >> dummy64; // ownerGuid (uint64)
body >> dummy32; // startbid
body >> dummy32; // outbid
body >> dummy32; // buyout
body >> dummy32; // timeLeftMs
body >> dummy64; // bidderGuid (uint64)
body >> dummy32; // curBid
for (size_t i = 0; i < 4u; ++i)
{
uint32 id = 0; body >> id;
if (id != orderedIds[i])
{
printf("ahbrowsepending FAIL: ordered id[%u]=%u\n",
unsigned(i), unsigned(id));
return 1;
}
body >> dummy32 >> dummy32 >> dummy32;
body >> dummy32 >> dummy32 >> dummy32;
body >> dummy64;
body >> dummy32 >> dummy32 >> dummy32 >> dummy32;
body >> dummy64;
body >> dummy32;
}
uint32 totalcount = 0; body >> totalcount;
if (totalcount != 3u) { printf("ahbrowsepending FAIL: totalcount %u\n", unsigned(totalcount)); return 1; }
if (totalcount != 4u)
{
printf("ahbrowsepending FAIL: totalcount %u\n",
unsigned(totalcount));
return 1;
}
(void)dummy32; (void)dummy64;
}

Expand Down
107 changes: 97 additions & 10 deletions src/modules/AhWorker/BrowseHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "Log/Log.h"
#include <cstdio>
#include <cstring>
#include <unordered_map>

namespace
{
Expand Down Expand Up @@ -121,6 +122,44 @@ namespace
namespace BrowseHandler
{

std::vector<BrowseRow> ComposeBidderRows(const std::vector<BrowseRow>& rows,
const BrowseQuery& q)
{
std::vector<BrowseRow> composed;
composed.reserve(q.outbidIds.size() + rows.size());

std::unordered_map<uint32, const BrowseRow*> byAuctionId;
byAuctionId.reserve(rows.size());
for (size_t i = 0; i < rows.size(); ++i)
{
byAuctionId[rows[i].entry.id] = &rows[i];
}

// Legacy parity: trust the validated client list, preserve its order and
// duplicates, and silently skip ids absent from this house-scoped result.
for (size_t i = 0; i < q.outbidIds.size(); ++i)
{
std::unordered_map<uint32, const BrowseRow*>::const_iterator it =
byAuctionId.find(q.outbidIds[i]);
if (it != byAuctionId.end())
{
composed.push_back(*it->second);
}
}

// Fetch orders rows by auction id, matching BuildListBidderItems' map
// traversal. An auction also present above intentionally appears twice.
for (size_t i = 0; i < rows.size(); ++i)
{
if (rows[i].entry.bidderGuidLow == q.requesterGuidLow)
{
composed.push_back(rows[i]);
}
}

return composed;
}

BrowseResult Fetch(ServiceDatabase& db, const BrowseQuery& q, FetchStatus& status)
{
status = FETCH_OK;
Expand Down Expand Up @@ -203,16 +242,36 @@ BrowseResult Fetch(ServiceDatabase& db, const BrowseQuery& q, FetchStatus& statu
sql += buf;
}
}
else
else if (q.kind == static_cast<uint8>(BROWSE_OWNER))
{
// OWNER or BIDDER: scope by the requester GUID.
char buf[96];
const char* col = (q.kind == static_cast<uint8>(BROWSE_BIDDER))
? "a.buyguid"
: "a.itemowner";
snprintf(buf, sizeof(buf), " AND %s = %u", col, q.requesterGuidLow);
snprintf(buf, sizeof(buf), " AND a.itemowner = %u", q.requesterGuidLow);
sql += buf;
}
else
{
// BIDDER needs both parts of the legacy reply: auctions currently bid
// by the requester plus client-supplied outbid ids. The decoded rows
// are composed in exact client-visible order after this single query.
char buf[64];
snprintf(buf, sizeof(buf), " AND (a.buyguid = %u", q.requesterGuidLow);
sql += buf;
if (!q.outbidIds.empty())
{
sql += " OR a.id IN (";
for (size_t i = 0; i < q.outbidIds.size(); ++i)
{
if (i != 0u)
{
sql += ',';
}
snprintf(buf, sizeof(buf), "%u", q.outbidIds[i]);
sql += buf;
}
sql += ')';
}
sql += ')';
}

sql += " ORDER BY a.id";

Expand Down Expand Up @@ -384,6 +443,11 @@ BrowseResult Fetch(ServiceDatabase& db, const BrowseQuery& q, FetchStatus& statu
while (result->NextRow());
delete result;

if (q.kind == static_cast<uint8>(BROWSE_BIDDER))
{
rows = BrowseHandler::ComposeBidderRows(rows, q);
}

status = rows.empty() ? FETCH_EMPTY : FETCH_OK;
return BrowseHandler::FilterAndPaginate(rows, q);
}
Expand Down Expand Up @@ -439,19 +503,32 @@ void BrowseThread::run()
}
IpcMessage rm;
rm.op = IPC_BROWSE_RESULT;
res.Encode(rm.body);
if (res.entries.size() > BrowseResult::MAX_ENTRIES)
{
BrowseResult capped;
capped.queryId = res.queryId;
capped.kind = res.kind;
capped.elunaPending = 0u;
capped.tooMany = 1u;
capped.totalcount = 0u;
capped.Encode(rm.body);
}
else
{
res.Encode(rm.body);
}
// I8: preflight outbound body size; never emit over the MAXLEN cap.
if (rm.body.size() > BrowseResult::MAX_WIRE)
{
// Should be impossible at cap 1000 entries; fail safe by
// Should be impossible within MAX_ENTRIES; fail safe by
// replying tooMany so mangosd tells the player the AH is
// unavailable (coordinator model: no in-process fallback).
BrowseResult capped;
capped.queryId = res.queryId;
capped.kind = res.kind;
capped.elunaPending = res.elunaPending;
capped.elunaPending = 0u;
capped.tooMany = 1u;
capped.totalcount = res.totalcount;
capped.totalcount = 0u;
rm.body.clear();
capped.Encode(rm.body);
}
Expand Down Expand Up @@ -519,6 +596,16 @@ namespace BrowseHandler
if (!isList)
{
// OWNER/BIDDER: every row is an entry.
if (res.entries.size() >= BrowseResult::MAX_ENTRIES)
{
// The IPC decoder rejects a larger count. Decline now so
// mangosd consumes the pending request and immediately
// reports the AH as unavailable instead of timing out.
res.tooMany = 1u;
res.entries.clear();
res.totalcount = 0u;
return res;
}
++res.totalcount;
res.entries.push_back(r.entry);
continue;
Expand Down
6 changes: 6 additions & 0 deletions src/modules/AhWorker/BrowseHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ enum FetchStatus

namespace BrowseHandler
{
/// PURE: compose a BIDDER result exactly like the legacy client path.
/// Client-supplied outbid ids are resolved in client order (including
/// duplicates), then the requester's current bids follow in row order.
std::vector<BrowseRow> ComposeBidderRows(const std::vector<BrowseRow>& rows,
const BrowseQuery& q);

/// PURE: usable filter (Task 6) + name filter (LIST) + pagination OR
/// defer-un-paginated. Cheap proto filters are assumed SQL-applied.
BrowseResult FilterAndPaginate(const std::vector<BrowseRow>& rows,
Expand Down
Loading
Loading