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) {
|
if (force) {
|
||||||
Repository.getDisconnectionQueue().remove(getName());
|
Repository.getDisconnectionQueue().remove(getName());
|
||||||
}
|
}
|
||||||
GameWorld.getLogoutListeners().forEach((it) -> it.logout(this));
|
if (!isArtificial())
|
||||||
|
GameWorld.getLogoutListeners().forEach((it) -> it.logout(this));
|
||||||
setPlaying(false);
|
setPlaying(false);
|
||||||
getWalkingQueue().reset();
|
getWalkingQueue().reset();
|
||||||
GameWorld.getLogoutListeners().forEach((it) -> it.logout(this));
|
|
||||||
if(!logoutListeners.isEmpty()){
|
if(!logoutListeners.isEmpty()){
|
||||||
logoutListeners.forEach((key,method) -> method.invoke(this));
|
logoutListeners.forEach((key,method) -> method.invoke(this));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import api.ShutdownListener;
|
|||||||
import core.game.node.entity.player.Player;
|
import core.game.node.entity.player.Player;
|
||||||
import rs09.Server;
|
import rs09.Server;
|
||||||
import rs09.ServerConstants;
|
import rs09.ServerConstants;
|
||||||
|
import rs09.game.ai.AIRepository;
|
||||||
import rs09.game.system.SystemLogger;
|
import rs09.game.system.SystemLogger;
|
||||||
import rs09.game.world.GameWorld;
|
import rs09.game.world.GameWorld;
|
||||||
import rs09.game.world.repository.Repository;
|
import rs09.game.world.repository.Repository;
|
||||||
@@ -34,8 +35,13 @@ public final class SystemTermination {
|
|||||||
public void terminate() {
|
public void terminate() {
|
||||||
SystemLogger.logInfo("[SystemTerminator] Initializing termination sequence - do not shutdown!");
|
SystemLogger.logInfo("[SystemTerminator] Initializing termination sequence - do not shutdown!");
|
||||||
try {
|
try {
|
||||||
|
SystemLogger.logInfo("[SystemTerminator] Stopping all bots...");
|
||||||
|
AIRepository.clearAllBots();
|
||||||
|
SystemLogger.logInfo("[SystemTerminator] Shutting down networking...");
|
||||||
Server.setRunning(false);
|
Server.setRunning(false);
|
||||||
Server.getReactor().terminate();
|
Server.getReactor().terminate();
|
||||||
|
SystemLogger.logInfo("[SystemTerminator] Stopping all pulses...");
|
||||||
|
GameWorld.getMajorUpdateWorker().stop();
|
||||||
for (Iterator<Player> it = Repository.getPlayers().iterator(); it.hasNext();) {
|
for (Iterator<Player> it = Repository.getPlayers().iterator(); it.hasNext();) {
|
||||||
try {
|
try {
|
||||||
Player p = it.next();
|
Player p = it.next();
|
||||||
|
|||||||
@@ -42,5 +42,9 @@ class AIRepository {
|
|||||||
fun getOffer(player: Player): GrandExchangeOffer? {
|
fun getOffer(player: Player): GrandExchangeOffer? {
|
||||||
return GEOffers[player]
|
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() {
|
fun noteAndTeleport() {
|
||||||
for (item in player.inventory.toArray()) {
|
for (item in player.inventory.toArray()) {
|
||||||
if (item == null) continue
|
if (item == null) continue
|
||||||
if (item.noteChange != item.id && item.noteChange != -1) {
|
if (item.definition.isUnnoted) {
|
||||||
player.inventory.remove(item)
|
player.inventory.remove(item)
|
||||||
player.inventory.add(Item(item.noteChange, item.amount))
|
player.inventory.add(Item(item.noteChange, item.amount))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import rs09.game.system.SystemLogger
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileReader
|
import java.io.FileReader
|
||||||
import java.sql.Connection
|
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
|
* Collection of methods for interacting with the grand exchange databases
|
||||||
@@ -20,6 +22,8 @@ object GEDB {
|
|||||||
public var connection: Connection? = null
|
public var connection: Connection? = null
|
||||||
private var initialized = false
|
private var initialized = false
|
||||||
private var connectionRefs = 0
|
private var connectionRefs = 0
|
||||||
|
private var obtainConnectionLock = ReentrantLock()
|
||||||
|
private var dbRunLock = ReentrantLock()
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
init(File(ServerConstants.GRAND_EXCHANGE_DATA_PATH + "grandexchange.db").absolutePath)
|
init(File(ServerConstants.GRAND_EXCHANGE_DATA_PATH + "grandexchange.db").absolutePath)
|
||||||
@@ -36,23 +40,32 @@ object GEDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic fun run(closure: (conn: Connection) -> Unit) {
|
@JvmStatic fun run(closure: (conn: Connection) -> Unit) {
|
||||||
|
dbRunLock.tryLock(10000L, TimeUnit.MILLISECONDS)
|
||||||
|
|
||||||
connectionRefs++
|
connectionRefs++
|
||||||
val con = connect()
|
val con = connect()
|
||||||
closure.invoke(con)
|
closure.invoke(con)
|
||||||
connectionRefs--
|
connectionRefs--
|
||||||
|
|
||||||
if(connectionRefs == 0) {
|
if(connectionRefs == 0) {
|
||||||
con.close()
|
con.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbRunLock.unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun connect(): Connection
|
private fun connect(): Connection
|
||||||
{
|
{
|
||||||
|
obtainConnectionLock.tryLock(10000L, TimeUnit.MILLISECONDS)
|
||||||
|
|
||||||
if (connection == null || connection!!.isClosed)
|
if (connection == null || connection!!.isClosed)
|
||||||
{
|
{
|
||||||
val ds = SQLiteDataSource()
|
val ds = SQLiteDataSource()
|
||||||
ds.url = "jdbc:sqlite:$pathString"
|
ds.url = "jdbc:sqlite:$pathString"
|
||||||
connection = ds.connection
|
connection = ds.connection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obtainConnectionLock.unlock()
|
||||||
return connection!!
|
return connection!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,37 +43,38 @@ class GrandExchange : StartupListener, Commands {
|
|||||||
GEDB.run { conn ->
|
GEDB.run { conn ->
|
||||||
val botStmt = conn.createStatement()
|
val botStmt = conn.createStatement()
|
||||||
val botOffers = botStmt.executeQuery("SELECT * from bot_offers")
|
val botOffers = botStmt.executeQuery("SELECT * from bot_offers")
|
||||||
while(botOffers.next()) {
|
while (botOffers.next()) {
|
||||||
val bot = GrandExchangeOffer.fromBotQuery(botOffers)
|
val bot = GrandExchangeOffer.fromBotQuery(botOffers)
|
||||||
val buyStmt = conn.createStatement()
|
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>()
|
val buyOffers = ArrayList<GrandExchangeOffer>()
|
||||||
while(buyOffer.next()) buyOffers.add(GrandExchangeOffer.fromQuery(buyOffer))
|
while (buyOffer.next()) buyOffers.add(GrandExchangeOffer.fromQuery(buyOffer))
|
||||||
buyStmt.close()
|
buyStmt.close()
|
||||||
|
|
||||||
for(offer in buyOffers.sortedBy { it.offeredValue }.reversed()) {
|
for (offer in buyOffers.sortedBy { it.offeredValue }.reversed()) {
|
||||||
if (bot.amountLeft <= 0) break
|
if (bot.amountLeft <= 0) break
|
||||||
exchange(bot, offer)
|
exchange(bot, offer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
botStmt.close()
|
botStmt.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
val activeOffers = ArrayList<GrandExchangeOffer>()
|
||||||
|
GEDB.run { conn ->
|
||||||
val stmt = conn.createStatement()
|
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 activeOffer =
|
||||||
val activeOffers = ArrayList<GrandExchangeOffer>()
|
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)
|
val offer = GrandExchangeOffer.fromQuery(activeOffer)
|
||||||
if(!offer.isActive) continue
|
if (!offer.isActive) continue
|
||||||
activeOffers.add(offer)
|
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
|
Thread.sleep(15_000) //sleep for 15 seconds
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ object PriceIndex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun allowItem(id: Int) {
|
fun allowItem(id: Int) {
|
||||||
|
if(canTrade(id)) return
|
||||||
|
|
||||||
GEDB.run { conn ->
|
GEDB.run { conn ->
|
||||||
val stmt = conn.prepareStatement(INSERT_QUERY)
|
val stmt = conn.prepareStatement(INSERT_QUERY)
|
||||||
stmt.setInt(1, id)
|
stmt.setInt(1, id)
|
||||||
|
|||||||
@@ -43,11 +43,10 @@ class LoginParser(val details: PlayerDetails, private val type: LoginType) {
|
|||||||
reconnect(player)
|
reconnect(player)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
lateinit var parser: PlayerSaveParser
|
||||||
try {
|
try {
|
||||||
val parser = PlayerParser.parse(player)
|
parser = PlayerParser.parse(player)
|
||||||
?: throw IllegalStateException("Failed parsing save for: " + player.username) //Parse core
|
?: 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)
|
catch (e: Exception)
|
||||||
{
|
{
|
||||||
@@ -74,6 +73,8 @@ class LoginParser(val details: PlayerDetails, private val type: LoginType) {
|
|||||||
if (!Repository.players.contains(player)) {
|
if (!Repository.players.contains(player)) {
|
||||||
Repository.addPlayer(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)
|
player.details.session.setObject(player)
|
||||||
flag(AuthResponse.Success)
|
flag(AuthResponse.Success)
|
||||||
player.init()
|
player.init()
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ class RottenPotatoRSHDDialogue(player: Player? = null) : DialoguePlugin(player)
|
|||||||
0 -> {
|
0 -> {
|
||||||
when (buttonId) {
|
when (buttonId) {
|
||||||
//Wipe Bots
|
//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
|
//Spawn Bots
|
||||||
2 -> ImmerseWorld.spawnBots().also { player.sendMessage(colorize("%RBots Respawning...")); end() }
|
2 -> ImmerseWorld.spawnBots().also { player.sendMessage(colorize("%RBots Respawning...")); end() }
|
||||||
//Force Log All Online Players
|
//Force Log All Online Players
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import kotlin.system.exitProcess
|
|||||||
* @author Ceikry
|
* @author Ceikry
|
||||||
*/
|
*/
|
||||||
class MajorUpdateWorker {
|
class MajorUpdateWorker {
|
||||||
|
var running: Boolean = false
|
||||||
var started = false
|
var started = false
|
||||||
val sequence = UpdateSequence()
|
val sequence = UpdateSequence()
|
||||||
val sdf = SimpleDateFormat("HHmmss")
|
val sdf = SimpleDateFormat("HHmmss")
|
||||||
@@ -39,7 +40,7 @@ class MajorUpdateWorker {
|
|||||||
Thread.currentThread().name = "Major Update Worker"
|
Thread.currentThread().name = "Major Update Worker"
|
||||||
started = true
|
started = true
|
||||||
Thread.sleep(600L)
|
Thread.sleep(600L)
|
||||||
while(true){
|
while(running){
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
Server.heartbeat()
|
Server.heartbeat()
|
||||||
|
|
||||||
@@ -75,6 +76,8 @@ class MajorUpdateWorker {
|
|||||||
ServerMonitor.eventQueue.add(GuiEvent.UpdatePulseCount(GameWorld.Pulser.TASKS.size))*/
|
ServerMonitor.eventQueue.add(GuiEvent.UpdatePulseCount(GameWorld.Pulser.TASKS.size))*/
|
||||||
Thread.sleep(max(600 - (end - start), 0))
|
Thread.sleep(max(600 - (end - start), 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SystemLogger.logInfo("Update worker stopped.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleTickActions() {
|
fun handleTickActions() {
|
||||||
@@ -127,10 +130,13 @@ class MajorUpdateWorker {
|
|||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
if(!started){
|
if(!started){
|
||||||
|
running = true
|
||||||
worker.start()
|
worker.start()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//if (ServerConstants.ALLOW_GUI)
|
fun stop() {
|
||||||
// ServerMonitor.open()
|
running = false
|
||||||
|
worker.interrupt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
import core.game.ge.OfferState
|
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.AfterAll
|
||||||
import org.junit.jupiter.api.Assertions
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.jupiter.api.Test
|
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.GEDB
|
||||||
import rs09.game.ge.GrandExchange
|
import rs09.game.ge.GrandExchange
|
||||||
import rs09.game.ge.GrandExchangeOffer
|
import rs09.game.ge.GrandExchangeOffer
|
||||||
|
import rs09.game.ge.PriceIndex
|
||||||
import rs09.game.system.SystemLogger
|
import rs09.game.system.SystemLogger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@@ -100,4 +104,15 @@ import kotlin.random.Random
|
|||||||
|
|
||||||
Assertions.assertEquals(defaultPrice, GrandExchange.getRecommendedPrice(4151))
|
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
|
package content
|
||||||
|
|
||||||
import TestUtils
|
import TestUtils
|
||||||
|
import core.game.node.item.Item
|
||||||
import org.junit.jupiter.api.Assertions
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.rs09.consts.Items
|
||||||
|
import rs09.game.content.ame.RandomEventNPC
|
||||||
import rs09.game.world.GameWorld
|
import rs09.game.world.GameWorld
|
||||||
|
|
||||||
class RandomEventManager {
|
class RandomEventManager {
|
||||||
@@ -33,4 +36,41 @@ class RandomEventManager {
|
|||||||
TestUtils.advanceTicks(rs09.game.content.ame.RandomEventManager.MAX_DELAY_TICKS + 5)
|
TestUtils.advanceTicks(rs09.game.content.ame.RandomEventManager.MAX_DELAY_TICKS + 5)
|
||||||
Assertions.assertNotNull(p.getAttribute("re-npc", null))
|
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