From 858fcd2a3ae8bcf3f1a405e88341bfb4f5a09554 Mon Sep 17 00:00:00 2001 From: P0nk Date: Sun, 11 Apr 2021 13:05:12 +0200 Subject: [PATCH 1/9] Actually perform name changes & world transfers on startup No changes were being committed --- src/main/java/net/server/Server.java | 61 ++++++++++++++++------------ 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index a8def9df3b..5efb31ad68 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -1575,18 +1575,23 @@ public class Server { PreparedStatement ps = con.prepareStatement("SELECT * FROM namechanges WHERE completionTime IS NULL"); ResultSet rs = ps.executeQuery()) { List> changedNames = new LinkedList<>(); //logging only - while (rs.next()) { - con.setAutoCommit(false); - int nameChangeId = rs.getInt("id"); - int characterId = rs.getInt("characterId"); - String oldName = rs.getString("old"); - String newName = rs.getString("new"); - boolean success = MapleCharacter.doNameChange(con, characterId, oldName, newName, nameChangeId); - if (!success) { - con.rollback(); //discard changes - } else { - changedNames.add(new Pair<>(oldName, newName)); + + con.setAutoCommit(false); + try { + while (rs.next()) { + int nameChangeId = rs.getInt("id"); + int characterId = rs.getInt("characterId"); + String oldName = rs.getString("old"); + String newName = rs.getString("new"); + boolean success = MapleCharacter.doNameChange(con, characterId, oldName, newName, nameChangeId); + if (!success) { + con.rollback(); //discard changes + } else { + con.commit(); + changedNames.add(new Pair<>(oldName, newName)); + } } + } finally { con.setAutoCommit(true); } //log @@ -1625,23 +1630,29 @@ public class Server { } rs.beforeFirst(); List>> worldTransfers = new LinkedList<>(); //logging only > - while (rs.next()) { - con.setAutoCommit(false); - int nameChangeId = rs.getInt("id"); - if (removedTransfers.contains(nameChangeId)) { - continue; - } - int characterId = rs.getInt("characterId"); - int oldWorld = rs.getInt("from"); - int newWorld = rs.getInt("to"); - boolean success = MapleCharacter.doWorldTransfer(con, characterId, oldWorld, newWorld, nameChangeId); - if (!success) { - con.rollback(); - } else { - worldTransfers.add(new Pair<>(characterId, new Pair<>(oldWorld, newWorld))); + + con.setAutoCommit(false); + try { + while (rs.next()) { + int nameChangeId = rs.getInt("id"); + if (removedTransfers.contains(nameChangeId)) { + continue; + } + int characterId = rs.getInt("characterId"); + int oldWorld = rs.getInt("from"); + int newWorld = rs.getInt("to"); + boolean success = MapleCharacter.doWorldTransfer(con, characterId, oldWorld, newWorld, nameChangeId); + if (!success) { + con.rollback(); + } else { + con.commit(); + worldTransfers.add(new Pair<>(characterId, new Pair<>(oldWorld, newWorld))); + } } + } finally { con.setAutoCommit(true); } + //log for (Pair> worldTransferPair : worldTransfers) { int charId = worldTransferPair.getLeft(); From 68239bc0b5351f9c31dc5a72da1fd3e35531cf18 Mon Sep 17 00:00:00 2001 From: P0nk Date: Sun, 11 Apr 2021 13:33:22 +0200 Subject: [PATCH 2/9] Make database operations during startup consistent Use the same Connection. and log things in a similar fashion --- .../manipulator/MapleCashidGenerator.java | 20 +++-- src/main/java/net/server/Server.java | 86 +++++++++---------- src/main/java/net/server/task/CouponTask.java | 14 ++- 3 files changed, 65 insertions(+), 55 deletions(-) diff --git a/src/main/java/client/inventory/manipulator/MapleCashidGenerator.java b/src/main/java/client/inventory/manipulator/MapleCashidGenerator.java index 274c2fdb8c..13096a9724 100644 --- a/src/main/java/client/inventory/manipulator/MapleCashidGenerator.java +++ b/src/main/java/client/inventory/manipulator/MapleCashidGenerator.java @@ -19,6 +19,8 @@ */ package client.inventory.manipulator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import tools.DatabaseConnection; import java.sql.Connection; @@ -32,7 +34,7 @@ import java.util.Set; * @author RonanLana */ public class MapleCashidGenerator { - + private static final Logger log = LoggerFactory.getLogger(MapleCashidGenerator.class); private final static Set existentCashids = new HashSet<>(10000); private static Integer runningCashid = 0; @@ -49,13 +51,9 @@ public class MapleCashidGenerator { } } - public static synchronized void loadExistentCashIdsFromDb() { - try (Connection con = DatabaseConnection.getConnection()) { - loadExistentCashIdsFromQuery(con, "SELECT id FROM rings"); - loadExistentCashIdsFromQuery(con, "SELECT petid FROM pets"); - } catch (SQLException ex) { - ex.printStackTrace(); - } + public static synchronized void loadExistentCashIdsFromDb(Connection con) throws SQLException { + loadExistentCashIdsFromQuery(con, "SELECT id FROM rings"); + loadExistentCashIdsFromQuery(con, "SELECT petid FROM pets"); runningCashid = 0; do { @@ -67,7 +65,11 @@ public class MapleCashidGenerator { runningCashid++; if (runningCashid >= 777000000) { existentCashids.clear(); - loadExistentCashIdsFromDb(); + try (Connection con = DatabaseConnection.getConnection()) { + loadExistentCashIdsFromDb(con); + } catch (SQLException e) { + log.warn("Failed to reset overflowing cash ids", e); + } } } diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index 5efb31ad68..af9d0093af 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -80,6 +80,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.time.Instant; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicLong; @@ -625,7 +626,7 @@ public class Server { } } - public void updateActiveCoupons() throws SQLException { + public void updateActiveCoupons(Connection con) throws SQLException { synchronized (activeCoupons) { activeCoupons.clear(); Calendar c = Calendar.getInstance(); @@ -633,23 +634,19 @@ public class Server { int weekDay = c.get(Calendar.DAY_OF_WEEK); int hourDay = c.get(Calendar.HOUR_OF_DAY); - try (Connection con = DatabaseConnection.getConnection()) { + int weekdayMask = (1 << weekDay); + PreparedStatement ps = con.prepareStatement("SELECT couponid FROM nxcoupons WHERE (activeday & ?) = ? AND starthour <= ? AND endhour > ?"); + ps.setInt(1, weekdayMask); + ps.setInt(2, weekdayMask); + ps.setInt(3, hourDay); + ps.setInt(4, hourDay); - int weekdayMask = (1 << weekDay); - PreparedStatement ps = con.prepareStatement("SELECT couponid FROM nxcoupons WHERE (activeday & ?) = ? AND starthour <= ? AND endhour > ?"); - ps.setInt(1, weekdayMask); - ps.setInt(2, weekdayMask); - ps.setInt(3, hourDay); - ps.setInt(4, hourDay); - - try (ResultSet rs = ps.executeQuery()) { - while (rs.next()) { - activeCoupons.add(rs.getInt("couponid")); - } + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + activeCoupons.add(rs.getInt("couponid")); } - } catch (SQLException ex) { - ex.printStackTrace(); } + } } @@ -837,26 +834,19 @@ public class Server { TimeZone.setDefault(TimeZone.getTimeZone(YamlConfig.config.server.TIMEZONE)); try (Connection con = DatabaseConnection.getConnection()) { - try (PreparedStatement ps = con.prepareStatement("UPDATE accounts SET loggedin = 0")) { - ps.executeUpdate(); - } - - try (PreparedStatement ps = con.prepareStatement("UPDATE characters SET HasMerchant = 0")) { - ps.executeUpdate(); - } - + setAllLoggedOut(con); + setAllMerchantsInactive(con); cleanNxcodeCoupons(con); loadCouponRates(con); - updateActiveCoupons(); + updateActiveCoupons(con); + MapleCashidGenerator.loadExistentCashIdsFromDb(con); + applyAllNameChanges(con); // -- name changes can be missed by INSTANT_NAME_CHANGE -- + applyAllWorldTransfers(con); } catch (SQLException sqle) { - sqle.printStackTrace(); + log.error("Failed to run all startup-bound database tasks", sqle); + throw new IllegalStateException(sqle); } - applyAllNameChanges(); // -- name changes can be missed by INSTANT_NAME_CHANGE -- - applyAllWorldTransfers(); - //MaplePet.clearMissingPetsFromDb(); // thanks Optimist for noticing this taking too long to run - MapleCashidGenerator.loadExistentCashIdsFromDb(); - ThreadManager.getInstance().start(); initializeTimelyTasks(); // aggregated method for timely tasks thanks to lxconan @@ -931,6 +921,18 @@ public class Server { } } + private static void setAllLoggedOut(Connection con) throws SQLException { + try (PreparedStatement ps = con.prepareStatement("UPDATE accounts SET loggedin = 0")) { + ps.executeUpdate(); + } + } + + private static void setAllMerchantsInactive(Connection con) throws SQLException { + try (PreparedStatement ps = con.prepareStatement("UPDATE characters SET HasMerchant = 0")) { + ps.executeUpdate(); + } + } + private void initializeTimelyTasks() { TimerManager tMan = TimerManager.getInstance(); tMan.start(); @@ -1570,9 +1572,8 @@ public class Server { } } - private static void applyAllNameChanges() { - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT * FROM namechanges WHERE completionTime IS NULL"); + private static void applyAllNameChanges(Connection con) throws SQLException { + try (PreparedStatement ps = con.prepareStatement("SELECT * FROM namechanges WHERE completionTime IS NULL"); ResultSet rs = ps.executeQuery()) { List> changedNames = new LinkedList<>(); //logging only @@ -1596,18 +1597,17 @@ public class Server { } //log for (Pair namePair : changedNames) { - FilePrinter.print(FilePrinter.CHANGE_CHARACTER_NAME, "Name change applied : from \"" + namePair.getLeft() + "\" to \"" + namePair.getRight() + "\" at " + Calendar.getInstance().getTime().toString()); + log.info("Name change applied - from: \"{}\" to \"{}\" at {}", namePair.getLeft(), namePair.getRight(), Instant.now()); } } catch (SQLException e) { - e.printStackTrace(); - FilePrinter.printError(FilePrinter.CHANGE_CHARACTER_NAME, e, "Failed to retrieve list of pending name changes."); + log.warn("Failed to retrieve list of pending name changes", e); + throw e; } } - private static void applyAllWorldTransfers() { - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT * FROM worldtransfers WHERE completionTime IS NULL", - ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + private static void applyAllWorldTransfers(Connection con) throws SQLException { + try (PreparedStatement ps = con.prepareStatement("SELECT * FROM worldtransfers WHERE completionTime IS NULL", + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); ResultSet rs = ps.executeQuery()) { List removedTransfers = new LinkedList<>(); while (rs.next()) { @@ -1658,11 +1658,11 @@ public class Server { int charId = worldTransferPair.getLeft(); int oldWorld = worldTransferPair.getRight().getLeft(); int newWorld = worldTransferPair.getRight().getRight(); - FilePrinter.print(FilePrinter.WORLD_TRANSFER, "World transfer applied : Character ID " + charId + " from World " + oldWorld + " to World " + newWorld + " at " + Calendar.getInstance().getTime().toString()); + log.info("World transfer applied - character id {} from world {} to world {} at {}", charId, oldWorld, newWorld, Instant.now()); } } catch (SQLException e) { - e.printStackTrace(); - FilePrinter.printError(FilePrinter.WORLD_TRANSFER, e, "Failed to retrieve list of pending world transfers."); + log.warn("Failed to retrieve list of pending world transfers", e); + throw e; } } diff --git a/src/main/java/net/server/task/CouponTask.java b/src/main/java/net/server/task/CouponTask.java index 4436b3c78d..cd622c8f0e 100644 --- a/src/main/java/net/server/task/CouponTask.java +++ b/src/main/java/net/server/task/CouponTask.java @@ -19,8 +19,12 @@ */ package net.server.task; +import java.sql.Connection; import java.sql.SQLException; import net.server.Server; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tools.DatabaseConnection; import tools.FilePrinter; /** @@ -28,13 +32,17 @@ import tools.FilePrinter; * @info Thread responsible for maintaining coupons EXP & DROP effects active */ public class CouponTask implements Runnable { + private static final Logger log = LoggerFactory.getLogger(CouponTask.class); + @Override public void run() { try { - Server.getInstance().updateActiveCoupons(); + try (Connection con = DatabaseConnection.getConnection()) { + Server.getInstance().updateActiveCoupons(con); + } Server.getInstance().commitActiveCoupons(); - } catch(SQLException sqle) { - FilePrinter.printError(FilePrinter.EXCEPTION_CAUGHT, "Unexpected SQL error: " + sqle.getMessage()); + } catch (SQLException sqle) { + log.error("Error updating coupon effects", sqle); } } } From ab56d18bd3cffb18c04ba733b67f04149c0fec22 Mon Sep 17 00:00:00 2001 From: P0nk Date: Sun, 11 Apr 2021 14:02:25 +0200 Subject: [PATCH 3/9] Log startup time --- src/main/java/net/server/Server.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index af9d0093af..f7348572c4 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -80,6 +80,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.time.Duration; import java.time.Instant; import java.util.*; import java.util.Map.Entry; @@ -821,6 +822,7 @@ public class Server { } public void init() { + Instant beforeInit = Instant.now(); log.info("Cosmic v{} starting up.", ServerConstants.VERSION); if (YamlConfig.config.server.SHUTDOWNHOOK) { @@ -909,8 +911,9 @@ public class Server { log.info("Listening on port 8484"); - log.info("Cosmic is now online."); online = true; + Duration initDuration = Duration.between(beforeInit, Instant.now()); + log.info("Cosmic is now online after {} ms.", initDuration.toMillis()); MapleSkillbookInformationProvider.getInstance(); OpcodeConstants.generateOpcodeNames(); From 415376ce0899eba3573f9edff6bb93e4d30a9b40 Mon Sep 17 00:00:00 2001 From: P0nk Date: Mon, 12 Apr 2021 21:06:05 +0200 Subject: [PATCH 4/9] Load skills async on startup --- src/main/java/client/SkillFactory.java | 17 ++++++++--------- src/main/java/net/server/Server.java | 21 ++++++++++++++++----- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/main/java/client/SkillFactory.java b/src/main/java/client/SkillFactory.java index 5f337e2d36..b41f8f5657 100644 --- a/src/main/java/client/SkillFactory.java +++ b/src/main/java/client/SkillFactory.java @@ -31,33 +31,32 @@ import java.util.HashMap; import java.util.Map; public class SkillFactory { - private static Map skills = new HashMap<>(); - private static MapleDataProvider datasource = MapleDataProviderFactory.getDataProvider(MapleDataProviderFactory.fileInWZPath("Skill.wz")); + private static volatile Map skills = new HashMap<>(); + private static final MapleDataProvider datasource = MapleDataProviderFactory.getDataProvider(MapleDataProviderFactory.fileInWZPath("Skill.wz")); public static Skill getSkill(int id) { - if (!skills.isEmpty()) { - return skills.get(id); - } - return null; + return skills.get(id); } public static void loadAllSkills() { + final Map loadedSkills = new HashMap<>(); final MapleDataDirectoryEntry root = datasource.getRoot(); - int skillid; for (MapleDataFileEntry topDir : root.getFiles()) { // Loop thru jobs if (topDir.getName().length() <= 8) { for (MapleData data : datasource.getData(topDir.getName())) { // Loop thru each jobs if (data.getName().equals("skill")) { for (MapleData data2 : data) { // Loop thru each jobs if (data2 != null) { - skillid = Integer.parseInt(data2.getName()); - skills.put(skillid, loadFromData(skillid, data2)); + int skillId = Integer.parseInt(data2.getName()); + loadedSkills.put(skillId, loadFromData(skillId, data2)); } } } } } } + + skills = loadedSkills; } private static Skill loadFromData(int id, MapleData data) { diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index f7348572c4..f0b0e96ff1 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -84,6 +84,9 @@ import java.time.Duration; import java.time.Instant; import java.util.*; import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; @@ -824,6 +827,7 @@ public class Server { public void init() { Instant beforeInit = Instant.now(); log.info("Cosmic v{} starting up.", ServerConstants.VERSION); + final ExecutorService initExecutor = Executors.newFixedThreadPool(10); if (YamlConfig.config.server.SHUTDOWNHOOK) { Runtime.getRuntime().addShutdownHook(new Thread(shutdown(false))); @@ -849,15 +853,13 @@ public class Server { throw new IllegalStateException(sqle); } + final List> futures = new ArrayList<>(); + futures.add(initExecutor.submit(() -> SkillFactory.loadAllSkills())); + ThreadManager.getInstance().start(); initializeTimelyTasks(); // aggregated method for timely tasks thanks to lxconan long timeToTake = System.currentTimeMillis(); - SkillFactory.loadAllSkills(); - final double skillLoadTime = (System.currentTimeMillis() - timeToTake) / 1000.0; - log.info("Skills loaded in {} seconds", skillLoadTime); - - timeToTake = System.currentTimeMillis(); CashItemFactory.getSpecialCashItems(); final double itemLoadTime = (System.currentTimeMillis() - timeToTake) / 1000.0; @@ -897,6 +899,15 @@ public class Server { log.info("Families loaded in {} seconds", familyLoadTime); } + for (Future future : futures) { + try { + future.get(); + } catch (Exception e) { + log.error("Failed to run all startup-bound loading tasks", e); + throw new IllegalStateException(e); + } + } + IoBuffer.setUseDirectBuffer(false); // join IO operations performed by lxconan IoBuffer.setAllocator(new SimpleBufferAllocator()); acceptor = new NioSocketAcceptor(); From a59b55db572a4037e5b664d8a3c402f9e11b3e0f Mon Sep 17 00:00:00 2001 From: P0nk Date: Mon, 12 Apr 2021 21:44:47 +0200 Subject: [PATCH 5/9] Load cash items async on startup --- src/main/java/net/server/Server.java | 7 +---- src/main/java/server/CashShop.java | 43 ++++++++++++++++------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index f0b0e96ff1..44333c1d38 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -855,17 +855,12 @@ public class Server { final List> futures = new ArrayList<>(); futures.add(initExecutor.submit(() -> SkillFactory.loadAllSkills())); + futures.add(initExecutor.submit(() -> CashItemFactory.loadAllCashItems())); ThreadManager.getInstance().start(); initializeTimelyTasks(); // aggregated method for timely tasks thanks to lxconan long timeToTake = System.currentTimeMillis(); - - CashItemFactory.getSpecialCashItems(); - final double itemLoadTime = (System.currentTimeMillis() - timeToTake) / 1000.0; - log.info("Items loaded in {} seconds", itemLoadTime); - - timeToTake = System.currentTimeMillis(); MapleQuest.loadAllQuest(); final double questLoadTime = (System.currentTimeMillis() - timeToTake) / 1000.0; log.info("Quest loaded in {} seconds", questLoadTime); diff --git a/src/main/java/server/CashShop.java b/src/main/java/server/CashShop.java index cdf024f316..a05942aa55 100644 --- a/src/main/java/server/CashShop.java +++ b/src/main/java/server/CashShop.java @@ -40,7 +40,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; -import java.util.Map.Entry; import java.util.concurrent.locks.Lock; /* @@ -144,15 +143,16 @@ public class CashShop { } public static class CashItemFactory { + private static volatile Map items = new HashMap<>(); + private static volatile List randomitemsns = new ArrayList<>(); + private static volatile Map> packages = new HashMap<>(); + private static volatile List specialcashitems = new ArrayList<>(); - private static final Map items = new HashMap<>(); - private static final Map> packages = new HashMap<>(); - private static final List specialcashitems = new ArrayList<>(); - private static final List randomitemsns = new ArrayList<>(); - - static { + public static void loadAllCashItems() { MapleDataProvider etc = MapleDataProviderFactory.getDataProvider(new File("wz/Etc.wz")); + Map loadedItems = new HashMap<>(); + List onSaleItems = new ArrayList<>(); for (MapleData item : etc.getData("Commodity.img").getChildren()) { int sn = MapleDataTool.getIntConvert("SN", item); int itemId = MapleDataTool.getIntConvert("ItemId", item); @@ -160,9 +160,16 @@ public class CashShop { long period = MapleDataTool.getIntConvert("Period", item, 1); short count = (short) MapleDataTool.getIntConvert("Count", item, 1); boolean onSale = MapleDataTool.getIntConvert("OnSale", item, 0) == 1; - items.put(sn, new CashItem(sn, itemId, price, period, count, onSale)); - } + loadedItems.put(sn, new CashItem(sn, itemId, price, period, count, onSale)); + if (onSale) { + onSaleItems.add(sn); + } + } + CashItemFactory.items = loadedItems; + CashItemFactory.randomitemsns = onSaleItems; + + Map> loadedPackages = new HashMap<>(); for (MapleData cashPackage : etc.getData("CashPackage.img").getChildren()) { List cPackage = new ArrayList<>(); @@ -170,24 +177,21 @@ public class CashShop { cPackage.add(Integer.parseInt(item.getData().toString())); } - packages.put(Integer.parseInt(cashPackage.getName()), cPackage); - } - - for (Entry e : items.entrySet()) { - if (e.getValue().isOnSale()) { - randomitemsns.add(e.getKey()); - } + loadedPackages.put(Integer.parseInt(cashPackage.getName()), cPackage); } + CashItemFactory.packages = loadedPackages; + List loadedSpecialItems = new ArrayList<>(); try (Connection con = DatabaseConnection.getConnection(); PreparedStatement ps = con.prepareStatement("SELECT * FROM specialcashitems"); ResultSet rs = ps.executeQuery()) { while (rs.next()) { - specialcashitems.add(new SpecialCashItem(rs.getInt("sn"), rs.getInt("modifier"), rs.getByte("info"))); + loadedSpecialItems.add(new SpecialCashItem(rs.getInt("sn"), rs.getInt("modifier"), rs.getByte("info"))); } } catch (SQLException ex) { ex.printStackTrace(); } + CashItemFactory.specialcashitems = loadedSpecialItems; } public static CashItem getRandomCashItem() { @@ -222,16 +226,17 @@ public class CashShop { } public static void reloadSpecialCashItems() {//Yay? - specialcashitems.clear(); + List loadedSpecialItems = new ArrayList<>(); try (Connection con = DatabaseConnection.getConnection(); PreparedStatement ps = con.prepareStatement("SELECT * FROM specialcashitems"); ResultSet rs = ps.executeQuery()) { while (rs.next()) { - specialcashitems.add(new SpecialCashItem(rs.getInt("sn"), rs.getInt("modifier"), rs.getByte("info"))); + loadedSpecialItems.add(new SpecialCashItem(rs.getInt("sn"), rs.getInt("modifier"), rs.getByte("info"))); } } catch (SQLException ex) { ex.printStackTrace(); } + CashItemFactory.specialcashitems = loadedSpecialItems; } } From 055ee01ae9b91f3254421e4cd1fa5838b5d5ae81 Mon Sep 17 00:00:00 2001 From: P0nk Date: Mon, 12 Apr 2021 22:05:57 +0200 Subject: [PATCH 6/9] Load quests async on startup --- src/main/java/net/server/Server.java | 8 +-- src/main/java/server/quest/MapleQuest.java | 74 ++++++++++------------ 2 files changed, 36 insertions(+), 46 deletions(-) diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index 44333c1d38..3051ee6781 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -856,15 +856,11 @@ public class Server { final List> futures = new ArrayList<>(); futures.add(initExecutor.submit(() -> SkillFactory.loadAllSkills())); futures.add(initExecutor.submit(() -> CashItemFactory.loadAllCashItems())); + futures.add(initExecutor.submit(() -> MapleQuest.loadAllQuests())); ThreadManager.getInstance().start(); initializeTimelyTasks(); // aggregated method for timely tasks thanks to lxconan - long timeToTake = System.currentTimeMillis(); - MapleQuest.loadAllQuest(); - final double questLoadTime = (System.currentTimeMillis() - timeToTake) / 1000.0; - log.info("Quest loaded in {} seconds", questLoadTime); - NewYearCardRecord.startPendingNewYearCardRequests(); if (YamlConfig.config.server.USE_THREAD_TRACKER) { @@ -888,7 +884,7 @@ public class Server { } if (YamlConfig.config.server.USE_FAMILY_SYSTEM) { - timeToTake = System.currentTimeMillis(); + long timeToTake = System.currentTimeMillis(); MapleFamily.loadAllFamilies(); final double familyLoadTime = (System.currentTimeMillis() - timeToTake) / 1000.0; log.info("Families loaded in {} seconds", familyLoadTime); diff --git a/src/main/java/server/quest/MapleQuest.java b/src/main/java/server/quest/MapleQuest.java index 73f08f5ed5..bff4ba65a2 100644 --- a/src/main/java/server/quest/MapleQuest.java +++ b/src/main/java/server/quest/MapleQuest.java @@ -21,20 +21,10 @@ */ package server.quest; -import java.io.File; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; import client.MapleCharacter; import client.MapleQuestStatus; import client.MapleQuestStatus.Status; import config.YamlConfig; -import java.util.Collection; -import java.util.EnumMap; -import java.util.Map.Entry; -import java.util.Set; import provider.MapleData; import provider.MapleDataProvider; import provider.MapleDataProviderFactory; @@ -44,6 +34,10 @@ import server.quest.requirements.*; import tools.MaplePacketCreator; import tools.StringUtil; +import java.io.File; +import java.util.*; +import java.util.Map.Entry; + /** * * @author Matze @@ -51,8 +45,8 @@ import tools.StringUtil; */ public class MapleQuest { - private static Map quests = new HashMap<>(); - private static Map infoNumberQuests = new HashMap<>(); + private static volatile Map quests = new HashMap<>(); + private static volatile Map infoNumberQuests = new HashMap<>(); private static Map medals = new HashMap<>(); private static final Set exploitableQuests = new HashSet<>(); @@ -459,9 +453,7 @@ public class MapleQuest { } public static void clearCache(int quest) { - if(quests.containsKey(quest)){ - quests.remove(quest); - } + quests.remove(quest); } public static void clearCache() { @@ -656,29 +648,31 @@ public class MapleQuest { return ret; } - - public static void loadAllQuest() { - try { - for(MapleData quest : questInfo.getChildren()) { - int questID = Integer.parseInt(quest.getName()); - - MapleQuest q = new MapleQuest(questID); - quests.put(questID, q); - - int infoNumber; - - infoNumber = q.getInfoNumber(Status.STARTED); - if (infoNumber > 0) { - infoNumberQuests.put(infoNumber, questID); - } - - infoNumber = q.getInfoNumber(Status.COMPLETED); - if (infoNumber > 0) { - infoNumberQuests.put(infoNumber, questID); - } - } - } catch (Exception ex) { - ex.printStackTrace(); - } - } + + public static void loadAllQuests() { + final Map loadedQuests = new HashMap<>(); + final Map loadedInfoNumberQuests = new HashMap<>(); + + for (MapleData quest : questInfo.getChildren()) { + int questID = Integer.parseInt(quest.getName()); + + MapleQuest q = new MapleQuest(questID); + loadedQuests.put(questID, q); + + int infoNumber; + + infoNumber = q.getInfoNumber(Status.STARTED); + if (infoNumber > 0) { + loadedInfoNumberQuests.put(infoNumber, questID); + } + + infoNumber = q.getInfoNumber(Status.COMPLETED); + if (infoNumber > 0) { + loadedInfoNumberQuests.put(infoNumber, questID); + } + } + + MapleQuest.quests = loadedQuests; + MapleQuest.infoNumberQuests = loadedInfoNumberQuests; + } } From dd7c2a697a658f584d0affe78a08a44145853989 Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 13 Apr 2021 08:14:11 +0200 Subject: [PATCH 7/9] Load skillbook source information async on startup --- src/main/java/net/server/Server.java | 4 +- .../scripting/npc/NPCConversationManager.java | 4 +- .../MapleSkillbookInformationProvider.java | 128 +++++++++--------- 3 files changed, 71 insertions(+), 65 deletions(-) diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index 3051ee6781..2a3f4c4921 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -853,10 +853,12 @@ public class Server { throw new IllegalStateException(sqle); } + // Run slow operations asynchronously to make startup faster final List> futures = new ArrayList<>(); futures.add(initExecutor.submit(() -> SkillFactory.loadAllSkills())); futures.add(initExecutor.submit(() -> CashItemFactory.loadAllCashItems())); futures.add(initExecutor.submit(() -> MapleQuest.loadAllQuests())); + futures.add(initExecutor.submit(() -> MapleSkillbookInformationProvider.loadAllSkillbookInformation())); ThreadManager.getInstance().start(); initializeTimelyTasks(); // aggregated method for timely tasks thanks to lxconan @@ -890,6 +892,7 @@ public class Server { log.info("Families loaded in {} seconds", familyLoadTime); } + // Wait on all async tasks to complete for (Future future : futures) { try { future.get(); @@ -917,7 +920,6 @@ public class Server { Duration initDuration = Duration.between(beforeInit, Instant.now()); log.info("Cosmic is now online after {} ms.", initDuration.toMillis()); - MapleSkillbookInformationProvider.getInstance(); OpcodeConstants.generateOpcodeNames(); CommandsExecutor.getInstance(); diff --git a/src/main/java/scripting/npc/NPCConversationManager.java b/src/main/java/scripting/npc/NPCConversationManager.java index 4c21dfc5c5..3788e5a2b7 100644 --- a/src/main/java/scripting/npc/NPCConversationManager.java +++ b/src/main/java/scripting/npc/NPCConversationManager.java @@ -570,7 +570,7 @@ public class NPCConversationManager extends AbstractPlayerInteraction { public Object[] getAvailableSkillBooks() { List ret = MapleItemInformationProvider.getInstance().usableSkillBooks(this.getPlayer()); - ret.addAll(MapleSkillbookInformationProvider.getInstance().getTeachableSkills(this.getPlayer())); + ret.addAll(MapleSkillbookInformationProvider.getTeachableSkills(this.getPlayer())); return ret.toArray(); } @@ -580,7 +580,7 @@ public class NPCConversationManager extends AbstractPlayerInteraction { } public String getSkillBookInfo(int itemid) { - SkillBookEntry sbe = MapleSkillbookInformationProvider.getInstance().getSkillbookAvailability(itemid); + SkillBookEntry sbe = MapleSkillbookInformationProvider.getSkillbookAvailability(itemid); switch (sbe) { case UNAVAILABLE: return ""; diff --git a/src/main/java/server/MapleSkillbookInformationProvider.java b/src/main/java/server/MapleSkillbookInformationProvider.java index 444f3563dc..e7909d7d1b 100644 --- a/src/main/java/server/MapleSkillbookInformationProvider.java +++ b/src/main/java/server/MapleSkillbookInformationProvider.java @@ -40,15 +40,12 @@ import java.util.regex.Pattern; * * @author RonanLana */ + +/** + * Only used in 1 script that gives players information about where skillbooks can be found + */ public class MapleSkillbookInformationProvider { - - private final static MapleSkillbookInformationProvider instance = new MapleSkillbookInformationProvider(); - - public static MapleSkillbookInformationProvider getInstance() { - return instance; - } - - protected static Map foundSkillbooks = new HashMap<>(); + private static volatile Map foundSkillbooks = new HashMap<>(); public enum SkillBookEntry { UNAVAILABLE, @@ -59,13 +56,15 @@ public class MapleSkillbookInformationProvider { SCRIPT } - private static String rootDirectory = "."; - - private static int skillbookMinItemid = 2280000; - private static int skillbookMaxItemid = 2300000; // exclusively - - static { - loadSkillbooks(); + private static final int SKILLBOOK_MIN_ITEMID = 2280000; + private static final int SKILLBOOK_MAX_ITEMID = 2300000; // exclusively + + public static void loadAllSkillbookInformation() { + Map loadedSkillbooks = new HashMap<>(); + loadedSkillbooks.putAll(fetchSkillbooksFromQuests()); + loadedSkillbooks.putAll(fetchSkillbooksFromReactors()); + loadedSkillbooks.putAll(fetchSkillbooksFromScripts()); + MapleSkillbookInformationProvider.foundSkillbooks = loadedSkillbooks; } private static boolean is4thJobSkill(int itemid) { @@ -73,7 +72,7 @@ public class MapleSkillbookInformationProvider { } private static boolean isSkillBook(int itemid) { - return itemid >= skillbookMinItemid && itemid < skillbookMaxItemid; + return itemid >= SKILLBOOK_MIN_ITEMID && itemid < SKILLBOOK_MAX_ITEMID; } private static boolean isQuestBook(int itemid) { @@ -86,9 +85,9 @@ public class MapleSkillbookInformationProvider { MapleData startReqItemData = questStartData.getChildByPath("item"); if (startReqItemData != null) { for (MapleData itemData : startReqItemData.getChildren()) { - int itemid = MapleDataTool.getInt("id", itemData, 0); - if (isQuestBook(itemid)) { - return itemid; + int itemId = MapleDataTool.getInt("id", itemData, 0); + if (isQuestBook(itemId)) { + return itemId; } } } @@ -115,40 +114,41 @@ public class MapleSkillbookInformationProvider { return -1; } - private static void fetchSkillbooksFromQuests() { + private static Map fetchSkillbooksFromQuests() { MapleDataProvider questDataProvider = MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/" + "Quest.wz")); MapleData actData = questDataProvider.getData("Act.img"); MapleData checkData = questDataProvider.getData("Check.img"); - + + final Map loadedSkillbooks = new HashMap<>(); for (MapleData questData : actData.getChildren()) { for (MapleData questStatusData : questData.getChildren()) { for (MapleData questNodeData : questStatusData.getChildren()) { String actNodeName = questNodeData.getName(); if (actNodeName.contentEquals("item")) { for (MapleData questItemData : questNodeData.getChildren()) { - int itemid = MapleDataTool.getInt("id", questItemData, 0); - int itemcount = MapleDataTool.getInt("count", questItemData, 0); + int itemId = MapleDataTool.getInt("id", questItemData, 0); + int itemCount = MapleDataTool.getInt("count", questItemData, 0); - if (isSkillBook(itemid) && itemcount > 0) { + if (isSkillBook(itemId) && itemCount > 0) { int questbook = fetchQuestbook(checkData, questData.getName()); if (questbook < 0) { - foundSkillbooks.put(itemid, SkillBookEntry.QUEST); + loadedSkillbooks.put(itemId, SkillBookEntry.QUEST); } else { - foundSkillbooks.put(itemid, SkillBookEntry.QUEST_BOOK); + loadedSkillbooks.put(itemId, SkillBookEntry.QUEST_BOOK); } } } } else if (actNodeName.contentEquals("skill")) { for (MapleData questSkillData : questNodeData.getChildren()) { - int skillid = MapleDataTool.getInt("id", questSkillData, 0); - if (is4thJobSkill(skillid)) { + int skillId = MapleDataTool.getInt("id", questSkillData, 0); + if (is4thJobSkill(skillId)) { // negative itemids are skill rewards int questbook = fetchQuestbook(checkData, questData.getName()); if (questbook < 0) { - foundSkillbooks.put(-skillid, SkillBookEntry.QUEST_REWARD); + loadedSkillbooks.put(-skillId, SkillBookEntry.QUEST_REWARD); } else { - foundSkillbooks.put(-skillid, SkillBookEntry.QUEST_BOOK); + loadedSkillbooks.put(-skillId, SkillBookEntry.QUEST_BOOK); } } } @@ -156,24 +156,30 @@ public class MapleSkillbookInformationProvider { } } } + + return loadedSkillbooks; } - private static void fetchSkillbooksFromReactors() { + private static Map fetchSkillbooksFromReactors() { + Map loadedSkillbooks = new HashMap<>(); + try (Connection con = DatabaseConnection.getConnection(); PreparedStatement ps = con.prepareStatement("SELECT itemid FROM reactordrops WHERE itemid >= ? AND itemid < ?;")) { - ps.setInt(1, skillbookMinItemid); - ps.setInt(2, skillbookMaxItemid); + ps.setInt(1, SKILLBOOK_MIN_ITEMID); + ps.setInt(2, SKILLBOOK_MAX_ITEMID); try (ResultSet rs = ps.executeQuery()) { if (rs.isBeforeFirst()) { while (rs.next()) { - foundSkillbooks.put(rs.getInt("itemid"), SkillBookEntry.REACTOR); + loadedSkillbooks.put(rs.getInt("itemid"), SkillBookEntry.REACTOR); } } } } catch (SQLException sqle) { sqle.printStackTrace(); } + + return loadedSkillbooks; } private static void listFiles(String directoryName, ArrayList files) { @@ -197,28 +203,20 @@ public class MapleSkillbookInformationProvider { return files; } - private static void filterScriptDirectorySearchMatchingData(String path) { - for (File file : listFilesFromDirectoryRecursively(rootDirectory + "/" + path)) { - if (file.getName().endsWith(".js")) { - fileSearchMatchingData(file); - } - } - } - - private static Set foundMatchingDataOnFile(String fileContent) { - Set matches = new HashSet<>(4); + private static Set findMatchingSkillbookIdsOnFile(String fileContent) { + Set skillbookIds = new HashSet<>(4); Matcher searchM = Pattern.compile("22(8|9)[0-9]{4}").matcher(fileContent); int idx = 0; while (searchM.find(idx)) { idx = searchM.end(); - matches.add(Integer.valueOf(fileContent.substring(searchM.start(), idx))); + skillbookIds.add(Integer.valueOf(fileContent.substring(searchM.start(), idx))); } - return matches; + return skillbookIds; } - static String readFileToString(File file, String encoding) throws IOException { + private static String readFileToString(File file, String encoding) throws IOException { Scanner scanner = new Scanner(file, encoding); String text = ""; try { @@ -232,36 +230,42 @@ public class MapleSkillbookInformationProvider { return text; } - private static void fileSearchMatchingData(File file) { + private static Map fileSearchMatchingData(File file) { + Map scriptFileSkillbooks = new HashMap<>(); + try { String fileContent = readFileToString(file, "UTF-8"); - Set books = foundMatchingDataOnFile(fileContent); - for (Integer i : books) { - foundSkillbooks.put(i, SkillBookEntry.SCRIPT); + Set skillbookIds = findMatchingSkillbookIdsOnFile(fileContent); + for (Integer skillbookId : skillbookIds) { + scriptFileSkillbooks.put(skillbookId, SkillBookEntry.SCRIPT); } } catch (IOException ioe) { System.out.println("Failed to read " + file.getName() + "."); ioe.printStackTrace(); } + + return scriptFileSkillbooks; } - private static void fetchSkillbooksFromScripts() { - filterScriptDirectorySearchMatchingData("scripts"); + private static Map fetchSkillbooksFromScripts() { + Map scriptSkillbooks = new HashMap<>(); + + for (File file : listFilesFromDirectoryRecursively("./scripts")) { + if (file.getName().endsWith(".js")) { + scriptSkillbooks.putAll(fileSearchMatchingData(file)); + } + } + + return scriptSkillbooks; } - private static void loadSkillbooks() { - fetchSkillbooksFromQuests(); - fetchSkillbooksFromReactors(); - fetchSkillbooksFromScripts(); - } - - public SkillBookEntry getSkillbookAvailability(int itemid) { - SkillBookEntry sbe = foundSkillbooks.get(itemid); + public static SkillBookEntry getSkillbookAvailability(int itemId) { + SkillBookEntry sbe = foundSkillbooks.get(itemId); return sbe != null ? sbe : SkillBookEntry.UNAVAILABLE; } - public List getTeachableSkills(MapleCharacter chr) { + public static List getTeachableSkills(MapleCharacter chr) { List list = new ArrayList<>(); for (Integer book : foundSkillbooks.keySet()) { From 8335e77f2e4a80de42d74d6322adcacfc48607df Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 13 Apr 2021 22:44:20 +0200 Subject: [PATCH 8/9] Load developer room async on startup --- src/main/java/net/server/Server.java | 6 +++--- .../server/life/MaplePlayerNPCFactory.java | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index 2a3f4c4921..e03a42caf1 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -139,7 +139,7 @@ public class Server { private final AtomicLong currentTime = new AtomicLong(0); private long serverCurrentTime = 0; - private boolean availableDeveloperRoom = false; + private volatile boolean availableDeveloperRoom = false; private boolean online = false; public static long uptime = System.currentTimeMillis(); @@ -827,7 +827,6 @@ public class Server { public void init() { Instant beforeInit = Instant.now(); log.info("Cosmic v{} starting up.", ServerConstants.VERSION); - final ExecutorService initExecutor = Executors.newFixedThreadPool(10); if (YamlConfig.config.server.SHUTDOWNHOOK) { Runtime.getRuntime().addShutdownHook(new Thread(shutdown(false))); @@ -853,12 +852,14 @@ public class Server { throw new IllegalStateException(sqle); } + final ExecutorService initExecutor = Executors.newFixedThreadPool(10); // Run slow operations asynchronously to make startup faster final List> futures = new ArrayList<>(); futures.add(initExecutor.submit(() -> SkillFactory.loadAllSkills())); futures.add(initExecutor.submit(() -> CashItemFactory.loadAllCashItems())); futures.add(initExecutor.submit(() -> MapleQuest.loadAllQuests())); futures.add(initExecutor.submit(() -> MapleSkillbookInformationProvider.loadAllSkillbookInformation())); + futures.add(initExecutor.submit(() -> MaplePlayerNPCFactory.loadFactoryMetadata())); ThreadManager.getInstance().start(); initializeTimelyTasks(); // aggregated method for timely tasks thanks to lxconan @@ -877,7 +878,6 @@ public class Server { } initWorldPlayerRanking(); - MaplePlayerNPCFactory.loadFactoryMetadata(); loadPlayerNpcMapStepFromDb(); } catch (Exception e) { e.printStackTrace();//For those who get errors diff --git a/src/main/java/server/life/MaplePlayerNPCFactory.java b/src/main/java/server/life/MaplePlayerNPCFactory.java index ea8da0658f..e769f52b8a 100644 --- a/src/main/java/server/life/MaplePlayerNPCFactory.java +++ b/src/main/java/server/life/MaplePlayerNPCFactory.java @@ -19,17 +19,18 @@ */ package server.life; -import java.io.File; -import java.util.HashMap; -import java.util.Map; -import java.util.List; -import java.util.LinkedList; import net.server.Server; import provider.MapleData; import provider.MapleDataProvider; import provider.MapleDataProviderFactory; import provider.MapleDataTool; +import java.io.File; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + /** * * @author RonanLana @@ -41,11 +42,11 @@ public class MaplePlayerNPCFactory { private static final Map> dnpcMaps = new HashMap<>(); private static Integer runningDeveloperOid = 2147483000; // 647 slots, long enough - public static boolean isExistentScriptid(int scriptid) { + public synchronized static boolean isExistentScriptid(int scriptid) { return npcData.getData(scriptid + ".img") != null; } - public static void loadDeveloperRoomMetadata(MapleDataProvider npc) { + private static void loadDeveloperRoomMetadata(MapleDataProvider npc) { MapleData thisData = npc.getData("9977777.img"); if(thisData != null) { MapleDataProvider map = MapleDataProviderFactory.getDataProvider(new File("wz/Map.wz")); @@ -65,7 +66,7 @@ public class MaplePlayerNPCFactory { } } - public static void loadFactoryMetadata() { + public synchronized static void loadFactoryMetadata() { MapleDataProvider npc = npcData; loadDeveloperRoomMetadata(npc); @@ -134,7 +135,7 @@ public class MaplePlayerNPCFactory { } } - public static List getDeveloperNpcsFromMapid(int mapid) { + public synchronized static List getDeveloperNpcsFromMapid(int mapid) { return dnpcMaps.get(mapid); } } From 5a5110d90581825b0f2936230ecee10a45b854a9 Mon Sep 17 00:00:00 2001 From: P0nk Date: Fri, 16 Apr 2021 20:37:51 +0200 Subject: [PATCH 9/9] Use existing connection for loading new year + family Move async tasks earlier for lower chance of having to wait --- src/main/java/client/MapleFamily.java | 164 +++++++++--------- .../client/newyear/NewYearCardRecord.java | 24 ++- src/main/java/net/server/Server.java | 102 +++++------ 3 files changed, 144 insertions(+), 146 deletions(-) diff --git a/src/main/java/client/MapleFamily.java b/src/main/java/client/MapleFamily.java index 8f1b008feb..60bf97b092 100644 --- a/src/main/java/client/MapleFamily.java +++ b/src/main/java/client/MapleFamily.java @@ -182,97 +182,95 @@ public class MapleFamily { } } - public static void loadAllFamilies() { - try(Connection con = DatabaseConnection.getConnection()) { - List, MapleFamilyEntry>> unmatchedJuniors = new ArrayList<>(200); // < familyEntry> - try(PreparedStatement psEntries = con.prepareStatement("SELECT * FROM family_character")) { - ResultSet rsEntries = psEntries.executeQuery(); - while(rsEntries.next()) { // can be optimized - int cid = rsEntries.getInt("cid"); - String name = null; - int level = -1; - int jobID = -1; - int world = -1; - try(PreparedStatement ps = con.prepareStatement("SELECT world, name, level, job FROM characters WHERE id = ?")) { - ps.setInt(1, cid); - ResultSet rs = ps.executeQuery(); - if(rs.next()) { - world = rs.getInt("world"); - name = rs.getString("name"); - level = rs.getInt("level"); - jobID = rs.getInt("job"); - } else { - FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Could not load character information of " + cid + " in loadAllFamilies(). (RECORD DOES NOT EXIST)"); - continue; - } - } catch(SQLException e) { - FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not load character information of " + cid + " in loadAllFamilies(). (SQL ERROR)"); - continue; - } - int familyid = rsEntries.getInt("familyid"); - int seniorid = rsEntries.getInt("seniorid"); - int reputation = rsEntries.getInt("reputation"); - int todaysRep = rsEntries.getInt("todaysrep"); - int totalRep = rsEntries.getInt("totalreputation"); - int repsToSenior = rsEntries.getInt("reptosenior"); - String precepts = rsEntries.getString("precepts"); - //Timestamp lastResetTime = rsEntries.getTimestamp("lastresettime"); //taken care of by FamilyDailyResetTask - World wserv = Server.getInstance().getWorld(world); - if (wserv == null) { - continue; - } - MapleFamily family = wserv.getFamily(familyid); - if(family == null) { - family = new MapleFamily(familyid, world); - Server.getInstance().getWorld(world).addFamily(familyid, family); - } - MapleFamilyEntry familyEntry = new MapleFamilyEntry(family, cid, name, level, MapleJob.getById(jobID)); - family.addEntry(familyEntry); - if(seniorid <= 0) { - family.setLeader(familyEntry); - family.setMessage(precepts, false); - } - MapleFamilyEntry senior = family.getEntryByID(seniorid); - if(senior != null) { - familyEntry.setSenior(family.getEntryByID(seniorid), false); + public static void loadAllFamilies(Connection con) { + List, MapleFamilyEntry>> unmatchedJuniors = new ArrayList<>(200); // < familyEntry> + try (PreparedStatement psEntries = con.prepareStatement("SELECT * FROM family_character")) { + ResultSet rsEntries = psEntries.executeQuery(); + while (rsEntries.next()) { // can be optimized + int cid = rsEntries.getInt("cid"); + String name = null; + int level = -1; + int jobID = -1; + int world = -1; + try (PreparedStatement ps = con.prepareStatement("SELECT world, name, level, job FROM characters WHERE id = ?")) { + ps.setInt(1, cid); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + world = rs.getInt("world"); + name = rs.getString("name"); + level = rs.getInt("level"); + jobID = rs.getInt("job"); } else { - if(seniorid > 0) unmatchedJuniors.add(new Pair<>(new Pair<>(world, seniorid), familyEntry)); - } - familyEntry.setReputation(reputation); - familyEntry.setTodaysRep(todaysRep); - familyEntry.setTotalReputation(totalRep); - familyEntry.setRepsToSenior(repsToSenior); - //load used entitlements - try (PreparedStatement ps = con.prepareStatement("SELECT entitlementid FROM family_entitlement WHERE charid = ?")) { - ps.setInt(1, familyEntry.getChrId()); - ResultSet rs = ps.executeQuery(); - while(rs.next()) { - familyEntry.setEntitlementUsed(rs.getInt("entitlementid")); - } + FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Could not load character information of " + cid + " in loadAllFamilies(). (RECORD DOES NOT EXIST)"); + continue; } + } catch (SQLException e) { + FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not load character information of " + cid + " in loadAllFamilies(). (SQL ERROR)"); + continue; } - } catch(SQLException e) { - FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not get family_character entries."); - e.printStackTrace(); - } - // link missing ones (out of order) - for(Pair, MapleFamilyEntry> unmatchedJunior : unmatchedJuniors) { - int world = unmatchedJunior.getLeft().getLeft(); - int seniorid = unmatchedJunior.getLeft().getRight(); - MapleFamilyEntry junior = unmatchedJunior.getRight(); - MapleFamilyEntry senior = Server.getInstance().getWorld(world).getFamily(junior.getFamily().getID()).getEntryByID(seniorid); - if(senior != null) { - junior.setSenior(senior, false); + int familyid = rsEntries.getInt("familyid"); + int seniorid = rsEntries.getInt("seniorid"); + int reputation = rsEntries.getInt("reputation"); + int todaysRep = rsEntries.getInt("todaysrep"); + int totalRep = rsEntries.getInt("totalreputation"); + int repsToSenior = rsEntries.getInt("reptosenior"); + String precepts = rsEntries.getString("precepts"); + //Timestamp lastResetTime = rsEntries.getTimestamp("lastresettime"); //taken care of by FamilyDailyResetTask + World wserv = Server.getInstance().getWorld(world); + if (wserv == null) { + continue; + } + MapleFamily family = wserv.getFamily(familyid); + if (family == null) { + family = new MapleFamily(familyid, world); + Server.getInstance().getWorld(world).addFamily(familyid, family); + } + MapleFamilyEntry familyEntry = new MapleFamilyEntry(family, cid, name, level, MapleJob.getById(jobID)); + family.addEntry(familyEntry); + if (seniorid <= 0) { + family.setLeader(familyEntry); + family.setMessage(precepts, false); + } + MapleFamilyEntry senior = family.getEntryByID(seniorid); + if (senior != null) { + familyEntry.setSenior(family.getEntryByID(seniorid), false); } else { - FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Missing senior for character " + junior.getName() + " in world " + world); + if (seniorid > 0) { + unmatchedJuniors.add(new Pair<>(new Pair<>(world, seniorid), familyEntry)); + } + } + familyEntry.setReputation(reputation); + familyEntry.setTodaysRep(todaysRep); + familyEntry.setTotalReputation(totalRep); + familyEntry.setRepsToSenior(repsToSenior); + //load used entitlements + try (PreparedStatement ps = con.prepareStatement("SELECT entitlementid FROM family_entitlement WHERE charid = ?")) { + ps.setInt(1, familyEntry.getChrId()); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + familyEntry.setEntitlementUsed(rs.getInt("entitlementid")); + } } } - } catch(SQLException e) { - FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not get DB connection."); + } catch (SQLException e) { + FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not get family_character entries."); e.printStackTrace(); } - for(World world : Server.getInstance().getWorlds()) { - for(MapleFamily family : world.getFamilies()) { + // link missing ones (out of order) + for (Pair, MapleFamilyEntry> unmatchedJunior : unmatchedJuniors) { + int world = unmatchedJunior.getLeft().getLeft(); + int seniorid = unmatchedJunior.getLeft().getRight(); + MapleFamilyEntry junior = unmatchedJunior.getRight(); + MapleFamilyEntry senior = Server.getInstance().getWorld(world).getFamily(junior.getFamily().getID()).getEntryByID(seniorid); + if (senior != null) { + junior.setSenior(senior, false); + } else { + FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Missing senior for character " + junior.getName() + " in world " + world); + } + } + + for (World world : Server.getInstance().getWorlds()) { + for (MapleFamily family : world.getFamilies()) { family.getLeader().doFullCount(); } } diff --git a/src/main/java/client/newyear/NewYearCardRecord.java b/src/main/java/client/newyear/NewYearCardRecord.java index f86de1b7da..88a7c0212d 100644 --- a/src/main/java/client/newyear/NewYearCardRecord.java +++ b/src/main/java/client/newyear/NewYearCardRecord.java @@ -339,22 +339,18 @@ public class NewYearCardRecord { } } } - - public static void startPendingNewYearCardRequests() { - try (Connection con = DatabaseConnection.getConnection()) { - try (PreparedStatement ps = con.prepareStatement("SELECT * FROM newyear WHERE timereceived = 0 AND senderdiscard = 0")) { - try (ResultSet rs = ps.executeQuery()) { - while (rs.next()) { - NewYearCardRecord newyear = new NewYearCardRecord(rs.getInt("senderid"), rs.getString("sendername"), rs.getInt("receiverid"), rs.getString("receivername"), rs.getString("message")); - newyear.setExtraNewYearCardRecord(rs.getInt("id"), rs.getBoolean("senderdiscard"), rs.getBoolean("receiverdiscard"), rs.getBoolean("received"), rs.getLong("timesent"), rs.getLong("timereceived")); - - Server.getInstance().setNewYearCard(newyear); - newyear.startNewYearCardTask(); - } + + public static void startPendingNewYearCardRequests(Connection con) throws SQLException { + try (PreparedStatement ps = con.prepareStatement("SELECT * FROM newyear WHERE timereceived = 0 AND senderdiscard = 0")) { + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + NewYearCardRecord newyear = new NewYearCardRecord(rs.getInt("senderid"), rs.getString("sendername"), rs.getInt("receiverid"), rs.getString("receivername"), rs.getString("message")); + newyear.setExtraNewYearCardRecord(rs.getInt("id"), rs.getBoolean("senderdiscard"), rs.getBoolean("receiverdiscard"), rs.getBoolean("received"), rs.getLong("timesent"), rs.getLong("timereceived")); + + Server.getInstance().setNewYearCard(newyear); + newyear.startNewYearCardTask(); } } - } catch(SQLException sqle) { - sqle.printStackTrace(); } } } diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index e03a42caf1..c053bbb369 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -704,7 +704,7 @@ public class Server { } private void installWorldPlayerRanking(int worldid) { - List>>> ranking = updatePlayerRankingFromDB(worldid); + List>>> ranking = loadPlayerRankingFromDB(worldid); if (!ranking.isEmpty()) { wldWLock.lock(); try { @@ -736,7 +736,7 @@ public class Server { wldWLock.unlock(); } } else { - List>>> ranking = updatePlayerRankingFromDB(-1 * (this.getWorldsSize() - 2)); // update ranking list + List>>> ranking = loadPlayerRankingFromDB(-1 * (this.getWorldsSize() - 2)); // update ranking list wldWLock.lock(); try { @@ -748,40 +748,47 @@ public class Server { } public void updateWorldPlayerRanking() { - List>>> rankUpdates = updatePlayerRankingFromDB(-1 * (this.getWorldsSize() - 1)); - if (!rankUpdates.isEmpty()) { - wldWLock.lock(); - try { - if (!YamlConfig.config.server.USE_WHOLE_SERVER_RANKING) { - for (int i = playerRanking.size(); i <= rankUpdates.get(rankUpdates.size() - 1).getLeft(); i++) { - playerRanking.add(new ArrayList<>(0)); - } - - for (Pair>> wranks : rankUpdates) { - playerRanking.set(wranks.getLeft(), wranks.getRight()); - } - } else { - playerRanking.set(0, rankUpdates.get(0).getRight()); - } - } finally { - wldWLock.unlock(); - } + List>>> rankUpdates = loadPlayerRankingFromDB(-1 * (this.getWorldsSize() - 1)); + if (rankUpdates.isEmpty()) { + return; } + + wldWLock.lock(); + try { + if (!YamlConfig.config.server.USE_WHOLE_SERVER_RANKING) { + for (int i = playerRanking.size(); i <= rankUpdates.get(rankUpdates.size() - 1).getLeft(); i++) { + playerRanking.add(new ArrayList<>(0)); + } + + for (Pair>> wranks : rankUpdates) { + playerRanking.set(wranks.getLeft(), wranks.getRight()); + } + } else { + playerRanking.set(0, rankUpdates.get(0).getRight()); + } + } finally { + wldWLock.unlock(); + } + } private void initWorldPlayerRanking() { if (YamlConfig.config.server.USE_WHOLE_SERVER_RANKING) { - playerRanking.add(new ArrayList<>(0)); + wldWLock.lock(); + try { + playerRanking.add(new ArrayList<>(0)); + } finally { + wldWLock.unlock(); + } } + updateWorldPlayerRanking(); } - private static List>>> updatePlayerRankingFromDB(int worldid) { + private static List>>> loadPlayerRankingFromDB(int worldid) { List>>> rankSystem = new ArrayList<>(); - List> rankUpdate = new ArrayList<>(0); try (Connection con = DatabaseConnection.getConnection()) { - String worldQuery; if (!YamlConfig.config.server.USE_WHOLE_SERVER_RANKING) { if (worldid >= 0) { @@ -793,6 +800,7 @@ public class Server { worldQuery = (" AND `characters`.`world` >= 0 AND `characters`.`world` <= " + Math.abs(worldid)); } + List> rankUpdate = new ArrayList<>(0); try (PreparedStatement ps = con.prepareStatement("SELECT `characters`.`name`, `characters`.`level`, `characters`.`world` FROM `characters` LEFT JOIN accounts ON accounts.id = characters.accountid WHERE `characters`.`gm` < 2 AND `accounts`.`banned` = '0'" + worldQuery + " ORDER BY " + (!YamlConfig.config.server.USE_WHOLE_SERVER_RANKING ? "world, " : "") + "level DESC, exp DESC, lastExpGainTime ASC LIMIT 50"); ResultSet rs = ps.executeQuery()) { @@ -836,22 +844,6 @@ public class Server { throw new IllegalStateException("Failed to initiate a connection to the database"); } - TimeZone.setDefault(TimeZone.getTimeZone(YamlConfig.config.server.TIMEZONE)); - - try (Connection con = DatabaseConnection.getConnection()) { - setAllLoggedOut(con); - setAllMerchantsInactive(con); - cleanNxcodeCoupons(con); - loadCouponRates(con); - updateActiveCoupons(con); - MapleCashidGenerator.loadExistentCashIdsFromDb(con); - applyAllNameChanges(con); // -- name changes can be missed by INSTANT_NAME_CHANGE -- - applyAllWorldTransfers(con); - } catch (SQLException sqle) { - log.error("Failed to run all startup-bound database tasks", sqle); - throw new IllegalStateException(sqle); - } - final ExecutorService initExecutor = Executors.newFixedThreadPool(10); // Run slow operations asynchronously to make startup faster final List> futures = new ArrayList<>(); @@ -861,11 +853,30 @@ public class Server { futures.add(initExecutor.submit(() -> MapleSkillbookInformationProvider.loadAllSkillbookInformation())); futures.add(initExecutor.submit(() -> MaplePlayerNPCFactory.loadFactoryMetadata())); + TimeZone.setDefault(TimeZone.getTimeZone(YamlConfig.config.server.TIMEZONE)); + + try (Connection con = DatabaseConnection.getConnection()) { + setAllLoggedOut(con); + setAllMerchantsInactive(con); + cleanNxcodeCoupons(con); + loadCouponRates(con); + updateActiveCoupons(con); + NewYearCardRecord.startPendingNewYearCardRequests(con); + MapleCashidGenerator.loadExistentCashIdsFromDb(con); + applyAllNameChanges(con); // -- name changes can be missed by INSTANT_NAME_CHANGE -- + applyAllWorldTransfers(con); + + if (YamlConfig.config.server.USE_FAMILY_SYSTEM) { + MapleFamily.loadAllFamilies(con); + } + } catch (SQLException sqle) { + log.error("Failed to run all startup-bound database tasks", sqle); + throw new IllegalStateException(sqle); + } + ThreadManager.getInstance().start(); initializeTimelyTasks(); // aggregated method for timely tasks thanks to lxconan - NewYearCardRecord.startPendingNewYearCardRequests(); - if (YamlConfig.config.server.USE_THREAD_TRACKER) { ThreadTracker.getInstance().registerThreadTrackerTask(); } @@ -885,13 +896,6 @@ public class Server { System.exit(0); } - if (YamlConfig.config.server.USE_FAMILY_SYSTEM) { - long timeToTake = System.currentTimeMillis(); - MapleFamily.loadAllFamilies(); - final double familyLoadTime = (System.currentTimeMillis() - timeToTake) / 1000.0; - log.info("Families loaded in {} seconds", familyLoadTime); - } - // Wait on all async tasks to complete for (Future future : futures) { try {