From c4781e80009459b610f127f73a6e63f82620def7 Mon Sep 17 00:00:00 2001 From: Ceikry Date: Sun, 3 Jul 2022 06:24:59 +0000 Subject: [PATCH] Reworked grand exchange offer processing Now much more efficient Offers will appear to complete almost instantaneously Players can no longer hoard bot offers as they will only be allocated at time of offer placement --- .../main/kotlin/rs09/game/ge/GrandExchange.kt | 148 ++++++++---------- .../kotlin/rs09/game/ge/GrandExchangeOffer.kt | 1 + .../game/interaction/inter/ge/StockMarket.kt | 2 - 3 files changed, 63 insertions(+), 88 deletions(-) diff --git a/Server/src/main/kotlin/rs09/game/ge/GrandExchange.kt b/Server/src/main/kotlin/rs09/game/ge/GrandExchange.kt index fb5584323..991ed1de8 100644 --- a/Server/src/main/kotlin/rs09/game/ge/GrandExchange.kt +++ b/Server/src/main/kotlin/rs09/game/ge/GrandExchange.kt @@ -5,7 +5,6 @@ import core.game.ge.OfferState import core.game.node.entity.player.Player import core.game.node.entity.player.info.PlayerDetails import core.game.node.entity.player.link.audio.Audio -import discord.Discord import rs09.ServerConstants import rs09.game.system.SystemLogger import rs09.game.system.command.Privilege @@ -14,12 +13,17 @@ import rs09.game.world.repository.Repository import rs09.tools.stringtools.colorize import java.lang.Integer.max import java.sql.ResultSet +import java.util.concurrent.LinkedBlockingDeque /** * Handles the exchanging of offers, offer update thread, etc. * @author Ceikry */ class GrandExchange : StartupListener, Commands { + private val GET_SPECIFIC_OFFER_BY_UID = "SELECT * FROM player_offers WHERE uid = ?;" + private val GET_MATCHES_FROM_PLAYER_OFFERS = "SELECT * FROM player_offers WHERE item_id = ? AND is_sale = ? AND offer_state < 4 AND NOT offer_state = 2;" + private val GET_MATCH_FROM_BOT_OFFERS = "SELECT * FROM bot_offers WHERE item_id = ?;" + /** * Fallback safety check to make sure we don't start the GE twice under any circumstance */ @@ -32,58 +36,65 @@ class GrandExchange : StartupListener, Commands { fun boot(){ if(isRunning) return - - SystemLogger.logGE("Initializing GE...") - //OfferManager.init() - SystemLogger.logGE("GE Initialized.") - SystemLogger.logGE("Initializing GE Update Worker") - val t = Thread { + Thread { Thread.currentThread().name = "GE Update Worker" while(true) { - GEDB.run { conn -> - val botStmt = conn.createStatement() - val botOffers = botStmt.executeQuery("SELECT * from bot_offers") - while (botOffers.next()) { - val bot = GrandExchangeOffer.fromBotQuery(botOffers) - val buyStmt = conn.createStatement() - val buyOffer = - buyStmt.executeQuery("SELECT * FROM player_offers WHERE item_id = ${bot.itemID} AND offer_state < 4 AND NOT offer_state = 2 AND offered_value >= ${bot.offeredValue}") - val buyOffers = ArrayList() - while (buyOffer.next()) buyOffers.add(GrandExchangeOffer.fromQuery(buyOffer)) - buyStmt.close() - - for (offer in buyOffers.sortedBy { it.offeredValue }.reversed()) { - if (bot.amountLeft <= 0) break - exchange(bot, offer) - } - } - botStmt.close() - } - - val activeOffers = ArrayList() - GEDB.run { conn -> - val stmt = conn.createStatement() - val activeOffer = - stmt.executeQuery("SELECT * from player_offers where offer_state < 4 AND NOT offer_state = 2 AND is_sale = true") - - while (activeOffer.next()) { - val offer = GrandExchangeOffer.fromQuery(activeOffer) - if (!offer.isActive) continue - activeOffers.add(offer) - } - } - for(offer in activeOffers) - processOffer(offer) - - Thread.sleep(15_000) //sleep for 15 seconds + var offer = pendingOffers.takeFirst() + offer.writeNew() + offer = getOfferByUid(offer.uid) ?: continue + selectPotentialMatches(offer).asSequence() + .sortedBy { if (offer.sell) -it.offeredValue else it.offeredValue } + .filter { if (offer.sell) it.offeredValue >= offer.offeredValue else it.offeredValue <= offer.offeredValue } + .forEach { match -> exchange(offer, match) } + if (!offer.isBot && offer.amountLeft > 0 && !offer.sell) + tryExchangeWithBots(offer) } }.start() isRunning = true } + private fun getOfferByUid(uid: Long): GrandExchangeOffer? { + var offer: GrandExchangeOffer? = null + GEDB.run { conn -> + val query = conn.prepareStatement(GET_SPECIFIC_OFFER_BY_UID) + query.setLong(1, uid) + val res = query.executeQuery() + + if (res.next()) + offer = GrandExchangeOffer.fromQuery(res) + } + return offer + } + + private fun tryExchangeWithBots(offer: GrandExchangeOffer) { + GEDB.run { conn -> + val query = conn.prepareStatement(GET_MATCH_FROM_BOT_OFFERS) + query.setInt(1, offer.itemID) + val res = query.executeQuery() + + if (res.next()) { + exchange(offer, GrandExchangeOffer.fromBotQuery(res).also { it.timeStamp = offer.timeStamp - 1L }) + } + } + } + + private fun selectPotentialMatches(offer: GrandExchangeOffer): List { + val matches = ArrayList() + GEDB.run { conn -> + val query = conn.prepareStatement(GET_MATCHES_FROM_PLAYER_OFFERS) + query.setInt(1, offer.itemID) + query.setBoolean(2, !offer.sell) + val res = query.executeQuery() + while (res.next()) { + matches.add(GrandExchangeOffer.fromQuery(res)) + } + } + return matches + } + override fun defineCommands() { define("addbotoffer", Privilege.ADMIN) {player, strings -> val id = strings[1].toInt() @@ -113,44 +124,7 @@ class GrandExchange : StartupListener, Commands { } companion object { - fun processOffer(offer: GrandExchangeOffer) - { - GEDB.run { conn -> - if(offer.isActive) { - val stmt = conn.createStatement() - val olderOffers = stmt.executeQuery("SELECT * FROM player_offers WHERE item_id = ${offer.itemID} AND is_sale = ${!offer.sell} AND offer_state < 4 AND NOT offer_state = 2 AND time_stamp < ${offer.timeStamp}") - if(tryOffers(offer, olderOffers, true)) return@run - val newerOffers = stmt.executeQuery("SELECT * FROM player_offers WHERE item_id = ${offer.itemID} AND is_sale = ${!offer.sell} AND offer_state < 4 AND NOT offer_state = 2 AND time_stamp >= ${offer.timeStamp}") - if(tryOffers(offer, newerOffers, false)) return@run - } - } - } - - private fun tryOffers(offer: GrandExchangeOffer, set: ResultSet, offerBiased: Boolean) : Boolean { - var bestOffer: GrandExchangeOffer? = null - while(set.next()) { - val otherOffer = GrandExchangeOffer.fromQuery(set) - if (otherOffer.amountLeft < 1 || offer.amountLeft < 1) continue - val buyOffer = if(otherOffer.sell) offer else otherOffer - val sellOffer = if(otherOffer.sell) otherOffer else offer - if (buyOffer.offeredValue < sellOffer.offeredValue) continue - bestOffer = if (bestOffer == null) otherOffer - else compareOffers(offer, otherOffer, bestOffer, offerBiased) - } - set.close() - if(bestOffer != null) exchange(offer, bestOffer) - return bestOffer != null - } - - private fun compareOffers(offer: GrandExchangeOffer, first: GrandExchangeOffer, second: GrandExchangeOffer, biased: Boolean) : GrandExchangeOffer { - return if(offer.sell) { - if(biased) if(first.offeredValue > second.offeredValue) first else second - else if(first.offeredValue < second.offeredValue) first else second - } else { - if(biased) if(first.offeredValue < second.offeredValue) first else second - else if(first.offeredValue > second.offeredValue) first else second - } - } + val pendingOffers = LinkedBlockingDeque() @JvmStatic fun getRecommendedPrice(itemID: Int, from_bot: Boolean = false): Int { @@ -234,7 +208,7 @@ class GrandExchange : StartupListener, Commands { return false val offer = GrandExchangeOffer.createBotOffer(itemID, amount) - offer.writeNew() + pendingOffers.addLast(offer) return true } @@ -266,15 +240,17 @@ class GrandExchange : StartupListener, Commands { Repository.sendNews(username + " just offered " + offer.amount + " " + getItemName(offer.itemID) + " on the GE.") } - offer.writeNew() + pendingOffers.add(offer) return true } fun exchange(offer: GrandExchangeOffer, other: GrandExchangeOffer) { - if(offer.sell && other.sell) return //Don't exchange if they are both sell offers + if(offer.sell == other.sell) return //Don't exchange if they are both buy/sell offers val amount = Integer.min(offer.amount - offer.completedAmount, other.amount - other.completedAmount) + if (amount == 0) return + val seller = if(offer.sell) offer else other val buyer = if(offer == seller) other else offer @@ -320,10 +296,10 @@ class GrandExchange : StartupListener, Commands { seller.update() val sellerPlayer = Repository.uid_map[seller.playerUID] - GrandExchangeRecords.getInstance(sellerPlayer).visualizeRecords() + sellerPlayer?.let { GrandExchangeRecords.getInstance(sellerPlayer).visualizeRecords() } buyer.update() val buyerPlayer = Repository.uid_map[buyer.playerUID] - GrandExchangeRecords.getInstance(buyerPlayer).visualizeRecords() + buyerPlayer?.let { GrandExchangeRecords.getInstance(buyerPlayer).visualizeRecords() } } private fun canUpdatePriceIndex(seller: GrandExchangeOffer, buyer: GrandExchangeOffer): Boolean { diff --git a/Server/src/main/kotlin/rs09/game/ge/GrandExchangeOffer.kt b/Server/src/main/kotlin/rs09/game/ge/GrandExchangeOffer.kt index 63906c5ca..beccb849c 100644 --- a/Server/src/main/kotlin/rs09/game/ge/GrandExchangeOffer.kt +++ b/Server/src/main/kotlin/rs09/game/ge/GrandExchangeOffer.kt @@ -143,6 +143,7 @@ class GrandExchangeOffer() { ) val nowuid = stmt.executeQuery("SELECT last_insert_rowid()") uid = nowuid.getLong(1) + GrandExchangeRecords.getInstance(player).offerRecords[index] = GrandExchangeRecords.OfferRecord(uid, index) visualize(player) stmt.close() diff --git a/Server/src/main/kotlin/rs09/game/interaction/inter/ge/StockMarket.kt b/Server/src/main/kotlin/rs09/game/interaction/inter/ge/StockMarket.kt index 4f73f15dd..613478598 100644 --- a/Server/src/main/kotlin/rs09/game/interaction/inter/ge/StockMarket.kt +++ b/Server/src/main/kotlin/rs09/game/interaction/inter/ge/StockMarket.kt @@ -258,7 +258,6 @@ class StockMarket : InterfaceListener { } if(GrandExchange.dispatch(player, offer)) { - GrandExchangeRecords.getInstance(player).offerRecords[offer.index] = GrandExchangeRecords.OfferRecord(offer.uid,offer.index) player.removeAttribute("ge-temp") } else @@ -277,7 +276,6 @@ class StockMarket : InterfaceListener { } if(GrandExchange.dispatch(player, offer) && removeItem(player, Item(995, total))) { - GrandExchangeRecords.getInstance(player).offerRecords[offer.index] = GrandExchangeRecords.OfferRecord(offer.uid, offer.index) player.removeAttribute("ge-temp") } }