Improved server shutdown order
Improved GE threading, introduced locks fixing server lags and GE offers not executing Moved player login hooks to the major update worker (from management server thread) Fixed random events in rare circumstances causing noted item loss
This commit is contained in:
@@ -368,10 +368,10 @@ public class Player extends Entity {
|
||||
if (force) {
|
||||
Repository.getDisconnectionQueue().remove(getName());
|
||||
}
|
||||
GameWorld.getLogoutListeners().forEach((it) -> it.logout(this));
|
||||
if (!isArtificial())
|
||||
GameWorld.getLogoutListeners().forEach((it) -> it.logout(this));
|
||||
setPlaying(false);
|
||||
getWalkingQueue().reset();
|
||||
GameWorld.getLogoutListeners().forEach((it) -> it.logout(this));
|
||||
if(!logoutListeners.isEmpty()){
|
||||
logoutListeners.forEach((key,method) -> method.invoke(this));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import api.ShutdownListener;
|
||||
import core.game.node.entity.player.Player;
|
||||
import rs09.Server;
|
||||
import rs09.ServerConstants;
|
||||
import rs09.game.ai.AIRepository;
|
||||
import rs09.game.system.SystemLogger;
|
||||
import rs09.game.world.GameWorld;
|
||||
import rs09.game.world.repository.Repository;
|
||||
@@ -34,8 +35,13 @@ public final class SystemTermination {
|
||||
public void terminate() {
|
||||
SystemLogger.logInfo("[SystemTerminator] Initializing termination sequence - do not shutdown!");
|
||||
try {
|
||||
SystemLogger.logInfo("[SystemTerminator] Stopping all bots...");
|
||||
AIRepository.clearAllBots();
|
||||
SystemLogger.logInfo("[SystemTerminator] Shutting down networking...");
|
||||
Server.setRunning(false);
|
||||
Server.getReactor().terminate();
|
||||
SystemLogger.logInfo("[SystemTerminator] Stopping all pulses...");
|
||||
GameWorld.getMajorUpdateWorker().stop();
|
||||
for (Iterator<Player> it = Repository.getPlayers().iterator(); it.hasNext();) {
|
||||
try {
|
||||
Player p = it.next();
|
||||
|
||||
@@ -42,5 +42,9 @@ class AIRepository {
|
||||
fun getOffer(player: Player): GrandExchangeOffer? {
|
||||
return GEOffers[player]
|
||||
}
|
||||
|
||||
@JvmStatic fun clearAllBots() {
|
||||
PulseRepository.toTypedArray().forEach { it.stop(); it.botScript.bot.clear(); AIPlayer.deregister((it.botScript.bot as AIPlayer).uid) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ abstract class RandomEventNPC(id: Int) : NPC(id) {
|
||||
fun noteAndTeleport() {
|
||||
for (item in player.inventory.toArray()) {
|
||||
if (item == null) continue
|
||||
if (item.noteChange != item.id && item.noteChange != -1) {
|
||||
if (item.definition.isUnnoted) {
|
||||
player.inventory.remove(item)
|
||||
player.inventory.add(Item(item.noteChange, item.amount))
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import rs09.game.system.SystemLogger
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
import java.sql.Connection
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
/**
|
||||
* Collection of methods for interacting with the grand exchange databases
|
||||
@@ -20,6 +22,8 @@ object GEDB {
|
||||
public var connection: Connection? = null
|
||||
private var initialized = false
|
||||
private var connectionRefs = 0
|
||||
private var obtainConnectionLock = ReentrantLock()
|
||||
private var dbRunLock = ReentrantLock()
|
||||
|
||||
fun init() {
|
||||
init(File(ServerConstants.GRAND_EXCHANGE_DATA_PATH + "grandexchange.db").absolutePath)
|
||||
@@ -36,23 +40,32 @@ object GEDB {
|
||||
}
|
||||
|
||||
@JvmStatic fun run(closure: (conn: Connection) -> Unit) {
|
||||
dbRunLock.tryLock(10000L, TimeUnit.MILLISECONDS)
|
||||
|
||||
connectionRefs++
|
||||
val con = connect()
|
||||
closure.invoke(con)
|
||||
connectionRefs--
|
||||
|
||||
if(connectionRefs == 0) {
|
||||
con.close()
|
||||
}
|
||||
|
||||
dbRunLock.unlock()
|
||||
}
|
||||
|
||||
private fun connect(): Connection
|
||||
{
|
||||
obtainConnectionLock.tryLock(10000L, TimeUnit.MILLISECONDS)
|
||||
|
||||
if (connection == null || connection!!.isClosed)
|
||||
{
|
||||
val ds = SQLiteDataSource()
|
||||
ds.url = "jdbc:sqlite:$pathString"
|
||||
connection = ds.connection
|
||||
}
|
||||
|
||||
obtainConnectionLock.unlock()
|
||||
return connection!!
|
||||
}
|
||||
|
||||
|
||||
@@ -43,37 +43,38 @@ class GrandExchange : StartupListener, Commands {
|
||||
GEDB.run { conn ->
|
||||
val botStmt = conn.createStatement()
|
||||
val botOffers = botStmt.executeQuery("SELECT * from bot_offers")
|
||||
while(botOffers.next()) {
|
||||
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 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<GrandExchangeOffer>()
|
||||
while(buyOffer.next()) buyOffers.add(GrandExchangeOffer.fromQuery(buyOffer))
|
||||
while (buyOffer.next()) buyOffers.add(GrandExchangeOffer.fromQuery(buyOffer))
|
||||
buyStmt.close()
|
||||
|
||||
for(offer in buyOffers.sortedBy { it.offeredValue }.reversed()) {
|
||||
for (offer in buyOffers.sortedBy { it.offeredValue }.reversed()) {
|
||||
if (bot.amountLeft <= 0) break
|
||||
exchange(bot, offer)
|
||||
}
|
||||
}
|
||||
botStmt.close()
|
||||
}
|
||||
|
||||
val activeOffers = ArrayList<GrandExchangeOffer>()
|
||||
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")
|
||||
val activeOffers = ArrayList<GrandExchangeOffer>()
|
||||
val activeOffer =
|
||||
stmt.executeQuery("SELECT * from player_offers where offer_state < 4 AND NOT offer_state = 2 AND is_sale = true")
|
||||
|
||||
while(activeOffer.next())
|
||||
{
|
||||
while (activeOffer.next()) {
|
||||
val offer = GrandExchangeOffer.fromQuery(activeOffer)
|
||||
if(!offer.isActive) continue
|
||||
if (!offer.isActive) continue
|
||||
activeOffers.add(offer)
|
||||
}
|
||||
|
||||
for(offer in activeOffers)
|
||||
processOffer(offer)
|
||||
|
||||
stmt.close()
|
||||
}
|
||||
for(offer in activeOffers)
|
||||
processOffer(offer)
|
||||
|
||||
Thread.sleep(15_000) //sleep for 15 seconds
|
||||
}
|
||||
}.start()
|
||||
|
||||
@@ -28,6 +28,8 @@ object PriceIndex {
|
||||
}
|
||||
|
||||
fun allowItem(id: Int) {
|
||||
if(canTrade(id)) return
|
||||
|
||||
GEDB.run { conn ->
|
||||
val stmt = conn.prepareStatement(INSERT_QUERY)
|
||||
stmt.setInt(1, id)
|
||||
|
||||
@@ -43,11 +43,10 @@ class LoginParser(val details: PlayerDetails, private val type: LoginType) {
|
||||
reconnect(player)
|
||||
return
|
||||
}
|
||||
lateinit var parser: PlayerSaveParser
|
||||
try {
|
||||
val parser = PlayerParser.parse(player)
|
||||
parser = PlayerParser.parse(player)
|
||||
?: throw IllegalStateException("Failed parsing save for: " + player.username) //Parse core
|
||||
loginListeners.forEach(Consumer { listener: LoginListener -> listener.login(player) }) //Run our login hooks
|
||||
parser.runContentHooks() //Run our saved-content-parsing hooks
|
||||
}
|
||||
catch (e: Exception)
|
||||
{
|
||||
@@ -74,6 +73,8 @@ class LoginParser(val details: PlayerDetails, private val type: LoginType) {
|
||||
if (!Repository.players.contains(player)) {
|
||||
Repository.addPlayer(player)
|
||||
}
|
||||
loginListeners.forEach(Consumer { listener: LoginListener -> listener.login(player) }) //Run our login hooks
|
||||
parser.runContentHooks() //Run our saved-content-parsing hooks
|
||||
player.details.session.setObject(player)
|
||||
flag(AuthResponse.Success)
|
||||
player.init()
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class RottenPotatoRSHDDialogue(player: Player? = null) : DialoguePlugin(player)
|
||||
0 -> {
|
||||
when (buttonId) {
|
||||
//Wipe Bots
|
||||
1 -> AIRepository.PulseRepository.toTypedArray().forEach { it.stop(); it.botScript.bot.clear(); AIPlayer.deregister((it.botScript.bot as AIPlayer).uid) }.also { player.sendMessage(colorize("%RBots wiped.")); end() }
|
||||
1 -> AIRepository.clearAllBots().also { player.sendMessage(colorize("%RBots wiped.")); end() }
|
||||
//Spawn Bots
|
||||
2 -> ImmerseWorld.spawnBots().also { player.sendMessage(colorize("%RBots Respawning...")); end() }
|
||||
//Force Log All Online Players
|
||||
|
||||
@@ -32,6 +32,7 @@ import kotlin.system.exitProcess
|
||||
* @author Ceikry
|
||||
*/
|
||||
class MajorUpdateWorker {
|
||||
var running: Boolean = false
|
||||
var started = false
|
||||
val sequence = UpdateSequence()
|
||||
val sdf = SimpleDateFormat("HHmmss")
|
||||
@@ -39,7 +40,7 @@ class MajorUpdateWorker {
|
||||
Thread.currentThread().name = "Major Update Worker"
|
||||
started = true
|
||||
Thread.sleep(600L)
|
||||
while(true){
|
||||
while(running){
|
||||
val start = System.currentTimeMillis()
|
||||
Server.heartbeat()
|
||||
|
||||
@@ -75,6 +76,8 @@ class MajorUpdateWorker {
|
||||
ServerMonitor.eventQueue.add(GuiEvent.UpdatePulseCount(GameWorld.Pulser.TASKS.size))*/
|
||||
Thread.sleep(max(600 - (end - start), 0))
|
||||
}
|
||||
|
||||
SystemLogger.logInfo("Update worker stopped.")
|
||||
}
|
||||
|
||||
fun handleTickActions() {
|
||||
@@ -127,10 +130,13 @@ class MajorUpdateWorker {
|
||||
|
||||
fun start() {
|
||||
if(!started){
|
||||
running = true
|
||||
worker.start()
|
||||
}
|
||||
}
|
||||
|
||||
//if (ServerConstants.ALLOW_GUI)
|
||||
// ServerMonitor.open()
|
||||
fun stop() {
|
||||
running = false
|
||||
worker.interrupt()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import core.game.ge.OfferState
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -7,6 +10,7 @@ import org.junit.jupiter.api.fail
|
||||
import rs09.game.ge.GEDB
|
||||
import rs09.game.ge.GrandExchange
|
||||
import rs09.game.ge.GrandExchangeOffer
|
||||
import rs09.game.ge.PriceIndex
|
||||
import rs09.game.system.SystemLogger
|
||||
import java.io.File
|
||||
import kotlin.random.Random
|
||||
@@ -100,4 +104,15 @@ import kotlin.random.Random
|
||||
|
||||
Assertions.assertEquals(defaultPrice, GrandExchange.getRecommendedPrice(4151))
|
||||
}
|
||||
|
||||
@Test fun concurrentlySubmittedOffersShouldNotThrowExceptions(){
|
||||
runBlocking {
|
||||
val a = GlobalScope.launch { for(i in 0 until 5) {PriceIndex.allowItem(i); GrandExchange.addBotOffer(i, 1)} }
|
||||
val b = GlobalScope.launch { for(i in 0 until 5) {PriceIndex.allowItem(i); GrandExchange.addBotOffer(i, 1)} }
|
||||
a.join()
|
||||
b.join()
|
||||
Assertions.assertEquals(false, a.isCancelled)
|
||||
Assertions.assertEquals(false, b.isCancelled)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package content
|
||||
|
||||
import TestUtils
|
||||
import core.game.node.item.Item
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.rs09.consts.Items
|
||||
import rs09.game.content.ame.RandomEventNPC
|
||||
import rs09.game.world.GameWorld
|
||||
|
||||
class RandomEventManager {
|
||||
@@ -33,4 +36,41 @@ class RandomEventManager {
|
||||
TestUtils.advanceTicks(rs09.game.content.ame.RandomEventManager.MAX_DELAY_TICKS + 5)
|
||||
Assertions.assertNotNull(p.getAttribute("re-npc", null))
|
||||
}
|
||||
|
||||
@Test fun teleportAndNotePunishmentShouldNotAffectAlreadyNotedItems() {
|
||||
val p = TestUtils.getMockPlayer("Shitforbrains")
|
||||
p.setAttribute("tutorial:complete", true)
|
||||
rs09.game.content.ame.RandomEventManager().login(p)
|
||||
|
||||
p.inventory.add(Item(Items.RAW_SHARK_384, 1000))
|
||||
rs09.game.content.ame.RandomEventManager.getInstance(p)?.fireEvent()
|
||||
p.getAttribute<RandomEventNPC>("re-npc")!!.noteAndTeleport()
|
||||
|
||||
Assertions.assertEquals(1000, p.inventory.getAmount(Items.RAW_SHARK_384))
|
||||
}
|
||||
|
||||
@Test fun teleportAndNotePunishmentShouldNoteNotableUnnotedItems() {
|
||||
val p = TestUtils.getMockPlayer("shitforbrains2")
|
||||
p.setAttribute("tutorial:complete", true)
|
||||
rs09.game.content.ame.RandomEventManager().login(p)
|
||||
|
||||
p.inventory.add(Item(4151, 5))
|
||||
rs09.game.content.ame.RandomEventManager.getInstance(p)?.fireEvent()
|
||||
p.getAttribute<RandomEventNPC>("re-npc")!!.noteAndTeleport()
|
||||
|
||||
Assertions.assertEquals(5, p.inventory.getAmount(4152))
|
||||
Assertions.assertEquals(0, p.inventory.getAmount(4151))
|
||||
}
|
||||
|
||||
@Test fun teleportAndNotePunishmentShouldNotAffectUnnotableItems() {
|
||||
val p = TestUtils.getMockPlayer("shitforbrains3")
|
||||
p.setAttribute("tutorial:complete", true)
|
||||
rs09.game.content.ame.RandomEventManager().login(p)
|
||||
|
||||
p.inventory.add(Item(Items.AIR_RUNE_556, 30))
|
||||
rs09.game.content.ame.RandomEventManager.getInstance(p)?.fireEvent()
|
||||
p.getAttribute<RandomEventNPC>("re-npc")!!.noteAndTeleport()
|
||||
|
||||
Assertions.assertEquals(30, p.inventory.getAmount(Items.AIR_RUNE_556))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user