From b6b9d3d744d43567dc5e8c94f6ee3307695ba5ba Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Thu, 18 May 2023 03:56:38 -0400 Subject: [PATCH] Multi-threaded resource loading --- .../java/emu/grasscutter/Grasscutter.java | 21 +- .../emu/grasscutter/data/ResourceLoader.java | 258 +++++++++++------- .../grasscutter/database/DatabaseManager.java | 16 +- .../game/drop/DropSystemLegacy.java | 4 +- .../game/expedition/ExpeditionSystem.java | 4 +- .../grasscutter/game/gacha/GachaSystem.java | 11 +- .../game/managers/energy/EnergyManager.java | 7 +- .../emu/grasscutter/game/shop/ShopSystem.java | 9 +- .../game/systems/AnnouncementSystem.java | 6 +- .../grasscutter/game/tower/TowerSystem.java | 4 +- .../game/world/WorldDataSystem.java | 6 +- .../emu/grasscutter/plugin/PluginManager.java | 11 +- .../grasscutter/server/game/GameServer.java | 33 ++- .../java/emu/grasscutter/tools/Tools.java | 17 +- .../java/emu/grasscutter/utils/Language.java | 54 ++-- 15 files changed, 282 insertions(+), 179 deletions(-) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index d4d579db1..c8297724c 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -1,8 +1,5 @@ package emu.grasscutter; -import static emu.grasscutter.config.Configuration.SERVER; -import static emu.grasscutter.utils.Language.translate; - import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import emu.grasscutter.auth.AuthenticationSystem; @@ -29,12 +26,6 @@ import emu.grasscutter.server.http.handlers.GenericHandler; import emu.grasscutter.server.http.handlers.LogHandler; import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.*; -import java.io.File; -import java.io.FileWriter; -import java.io.IOError; -import java.io.IOException; -import java.util.Calendar; -import javax.annotation.Nullable; import lombok.Getter; import lombok.Setter; import org.jline.reader.EndOfFileException; @@ -46,6 +37,16 @@ import org.jline.terminal.TerminalBuilder; import org.reflections.Reflections; import org.slf4j.LoggerFactory; +import javax.annotation.Nullable; +import java.io.File; +import java.io.FileWriter; +import java.io.IOError; +import java.io.IOException; +import java.util.Calendar; + +import static emu.grasscutter.config.Configuration.SERVER; +import static emu.grasscutter.utils.Language.translate; + public final class Grasscutter { public static final File configFile = new File("./config.json"); public static final Reflections reflector = new Reflections("emu.grasscutter"); @@ -120,7 +121,7 @@ public final class Grasscutter { } // Initialize database. - DatabaseManager.initialize(); + DatabaseManager.initializeAsync(); // Initialize the default systems. authenticationSystem = new DefaultAuthentication(); diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index cbbc1cc89..d36b89add 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -1,9 +1,5 @@ package emu.grasscutter.data; -import static emu.grasscutter.utils.FileUtils.getDataPath; -import static emu.grasscutter.utils.FileUtils.getResourcePath; -import static emu.grasscutter.utils.Language.translate; - import com.google.gson.annotations.SerializedName; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.binout.*; @@ -28,27 +24,34 @@ import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.JsonUtils; +import emu.grasscutter.utils.Language; import emu.grasscutter.utils.TsvUtils; import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArraySet; +import lombok.SneakyThrows; +import lombok.val; +import org.reflections.Reflections; + +import javax.script.Bindings; +import javax.script.CompiledScript; import java.io.IOException; import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArraySet; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.script.Bindings; -import javax.script.CompiledScript; -import lombok.SneakyThrows; -import lombok.val; -import org.reflections.Reflections; + +import static emu.grasscutter.utils.FileUtils.getDataPath; +import static emu.grasscutter.utils.FileUtils.getResourcePath; +import static emu.grasscutter.utils.Language.translate; public final class ResourceLoader { @@ -97,87 +100,136 @@ public final class ResourceLoader { return List.copyOf(map.values()); } + /** + * Runs a task asynchronously. + * + * @param task The task to run. + * @return A CompletableFuture that will complete when the task is done. + */ + public static CompletableFuture runAsync(Runnable task) { + return CompletableFuture.supplyAsync( + () -> { + task.run(); + return true; + }); + } + + /** + * Waits for all futures to complete. + * + * @param futures The futures to wait for. + */ + public static void waitForAll(Collection> futures) { + futures.forEach(CompletableFuture::join); + } + @SneakyThrows public static void loadAll() { if (loadedAll) return; Grasscutter.getLogger().info(translate("messages.status.resources.loading")); + // Mark the starting time. + var startTime = System.nanoTime(); + // Initialize the script loader. ScriptLoader.init(); - loadConfigData(); - // Load ability lists - loadAbilityEmbryos(); - loadOpenConfig(); - loadAbilityModifiers(); - // Load resources - loadResources(true); - // Process into depots - GameDepot.load(); - // Load spawn data and quests - loadSpawnData(); - loadQuests(); - loadScriptSceneData(); - // Load scene points - must be done AFTER resources are loaded - loadScenePoints(); + // Load 'TextMaps'. + var textMaps = ResourceLoader.runAsync(Language::loadTextMaps); + // Load 'BinOutput'. + var binOutput = ResourceLoader.loadConfigData(); + // Load 'ExcelBinOutput'. + var errors = new ConcurrentLinkedQueue>(); + var excelBinOutput = ResourceLoader.loadResources(true, errors); + // Load ability lists. + var abilities = ResourceLoader.runAsync(() -> { + ResourceLoader.loadAbilityEmbryos(); + ResourceLoader.loadOpenConfig(); + ResourceLoader.loadAbilityModifiers(); + }); + // Load spawn data and quests. + var scene = ResourceLoader.runAsync(() -> { + ResourceLoader.loadSpawnData(); + ResourceLoader.loadQuests(); + ResourceLoader.loadScriptSceneData(); + }); // Load default home layout - loadHomeworldDefaultSaveData(); - loadNpcBornData(); - loadBlossomResources(); - cacheTalentLevelSets(); + var entities = ResourceLoader.runAsync(() -> { + ResourceLoader.loadHomeworldDefaultSaveData(); + ResourceLoader.loadNpcBornData(); + ResourceLoader.loadBlossomResources(); + ResourceLoader.cacheTalentLevelSets(); + }); // Load custom server resources. - loadConfigLevelEntityData(); - loadQuestShareConfig(); - loadGadgetMappings(); - loadActivityCondGroups(); - loadGroupReplacements(); - loadTrialAvatarCustomData(); + var customServer = ResourceLoader.runAsync(() -> { + ResourceLoader.loadConfigLevelEntityData(); + ResourceLoader.loadQuestShareConfig(); + ResourceLoader.loadGadgetMappings(); + ResourceLoader.loadActivityCondGroups(); + ResourceLoader.loadGroupReplacements(); + ResourceLoader.loadTrialAvatarCustomData(); - EntityControllerScriptManager.load(); + EntityControllerScriptManager.load(); + }); + + // Wait for all futures to complete. + var futures = new ArrayList<>(List.of(textMaps, abilities, scene, entities, customServer)); + futures.addAll(binOutput); + futures.addAll(excelBinOutput); + ResourceLoader.waitForAll(futures); + + // Load dependent-resources. + GameDepot.load(); // Process into depots + ResourceLoader.loadScenePoints(); // Load scene points. + + // Log any errors. + errors.forEach( + pair -> + Grasscutter.getLogger() + .error("Error loading resource file: " + pair.left(), pair.right() + ".")); + + // Calculate the ending time. + var endTime = System.nanoTime(); + var ns = (endTime - startTime); // divide by 1000000 to get milliseconds. + Grasscutter.getLogger().debug("Loading resources took " + ns + "ns (" + ns / 1000000 + "ms)."); Grasscutter.getLogger().info(translate("messages.status.resources.finish")); loadedAll = true; } public static void loadResources() { - loadResources(false); + loadResources(false, new ConcurrentLinkedQueue<>()); } - public static void loadResources(boolean doReload) { - long startTime = System.nanoTime(); - val errors = - new ConcurrentLinkedQueue< - Pair>(); // Logger in a parallel stream will deadlock + /** + * Loads all resources from annotated classes. + * + * @param doReload Whether to reload resources. + */ + public static List> loadResources(boolean doReload, Queue> errors) { + // Load all resources in parallel. + return ResourceLoader.getResourceDefClassesPrioritySets().stream() + .map(classes -> classes.stream() + .parallel().unordered() + .map(c -> { + var type = c.getAnnotation(ResourceType.class); + if (type == null) return null; - getResourceDefClassesPrioritySets() - .forEach( - classes -> { - classes.stream() - .parallel() - .unordered() - .forEach( - c -> { - val type = c.getAnnotation(ResourceType.class); - if (type == null) return; + var map = GameData.getMapByResourceDef(c); + if (map == null) return null; - val map = GameData.getMapByResourceDef(c); - if (map == null) return; - - try { - loadFromResource(c, type, map, doReload); - } catch (Exception e) { - errors.add(Pair.of(Arrays.toString(type.name()), e)); - } - }); + return ResourceLoader.runAsync(() -> { + try { + loadFromResource(c, type, map, doReload); + } catch (Exception e) { + errors.add(Pair.of(Arrays.toString(type.name()), e)); + } }); - errors.forEach( - pair -> - Grasscutter.getLogger() - .error("Error loading resource file: " + pair.left(), pair.right())); - long endTime = System.nanoTime(); - long ns = (endTime - startTime); // divide by 1000000 to get milliseconds. - Grasscutter.getLogger().debug("Loading resources took " + ns + "ns == " + ns / 1000000 + "ms"); + }) + .toList() + ).flatMap(Collection::stream) + .toList(); } @SuppressWarnings("rawtypes") @@ -225,38 +277,35 @@ public final class ResourceLoader { private static void loadScenePoints() { val pattern = Pattern.compile("scene([0-9]+)_point\\.json"); - try { - Files.newDirectoryStream(getResourcePath("BinOutput/Scene/Point"), "scene*_point.json") - .forEach( - path -> { - val matcher = pattern.matcher(path.getFileName().toString()); - if (!matcher.find()) return; - int sceneId = Integer.parseInt(matcher.group(1)); + try (var stream = Files.newDirectoryStream(getResourcePath("BinOutput/Scene/Point"), "scene*_point.json")) { + stream.forEach(path -> { + var matcher = pattern.matcher(path.getFileName().toString()); + if (!matcher.find()) return; + var sceneId = Integer.parseInt(matcher.group(1)); - ScenePointConfig config; - try { - config = JsonUtils.loadToClass(path, ScenePointConfig.class); - } catch (Exception e) { - e.printStackTrace(); - return; - } + ScenePointConfig config; try { + config = JsonUtils.loadToClass(path, ScenePointConfig.class); + } catch (Exception e) { + e.printStackTrace(); + return; + } - if (config.points == null) return; + if (config.points == null) return; - val scenePoints = new IntArrayList(); - config.points.forEach( - (pointId, pointData) -> { - val scenePoint = new ScenePointEntry(sceneId, pointData); - scenePoints.add((int) pointId); - pointData.setId(pointId); + var scenePoints = new IntArrayList(); + config.points.forEach( + (pointId, pointData) -> { + var scenePoint = new ScenePointEntry(sceneId, pointData); + scenePoints.add((int) pointId); + pointData.setId(pointId); - GameData.getScenePointIdList().add((int) pointId); - GameData.getScenePointEntryMap().put((sceneId << 16) + pointId, scenePoint); + GameData.getScenePointIdList().add((int) pointId); + GameData.getScenePointEntryMap().put((sceneId << 16) + pointId, scenePoint); - pointData.updateDailyDungeon(); - }); - GameData.getScenePointsPerScene().put(sceneId, scenePoints); - }); + pointData.updateDailyDungeon(); + }); + GameData.getScenePointsPerScene().put(sceneId, scenePoints); + }); } catch (IOException ignored) { Grasscutter.getLogger() .error("Scene point files cannot be found, you cannot use teleport waypoints!"); @@ -607,12 +656,21 @@ public final class ResourceLoader { } } - private static void loadConfigData() { - loadConfigData(GameData.getAvatarConfigData(), "BinOutput/Avatar/", ConfigEntityAvatar.class); - loadConfigData( - GameData.getMonsterConfigData(), "BinOutput/Monster/", ConfigEntityMonster.class); - loadConfigDataMap( - GameData.getGadgetConfigData(), "BinOutput/Gadget/", ConfigEntityGadget.class); + /** + * Loads data from parsed files. + */ + private static List> loadConfigData() { + var tasks = new ArrayList>(); + + // Load config data. + tasks.add(ResourceLoader.runAsync(() -> + loadConfigData(GameData.getAvatarConfigData(), "BinOutput/Avatar/", ConfigEntityAvatar.class))); + tasks.add(ResourceLoader.runAsync(() -> + loadConfigData(GameData.getMonsterConfigData(), "BinOutput/Monster/", ConfigEntityMonster.class))); + tasks.add(ResourceLoader.runAsync(() -> + loadConfigDataMap(GameData.getGadgetConfigData(), "BinOutput/Gadget/", ConfigEntityGadget.class))); + + return tasks; } private static void loadConfigData( diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java index 45ad073fa..b6ec2b8f9 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseManager.java +++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java @@ -1,7 +1,5 @@ package emu.grasscutter.database; -import static emu.grasscutter.config.Configuration.DATABASE; - import com.mongodb.MongoCommandException; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -18,6 +16,8 @@ import emu.grasscutter.Grasscutter.ServerRunMode; import emu.grasscutter.game.Account; import org.reflections.Reflections; +import static emu.grasscutter.config.Configuration.DATABASE; + public final class DatabaseManager { private static Datastore gameDatastore; private static Datastore dispatchDatastore; @@ -35,6 +35,18 @@ public final class DatabaseManager { return getGameDatastore().getDatabase(); } + /** + * Performs the database initialization process. + * This occurs on a separate thread. + */ + public static void initializeAsync() { + new Thread(DatabaseManager::initialize).start(); + } + + /** + * Performs the database initialization process. + * This method is blocking. + */ public static void initialize() { // Initialize MongoClient gameMongoClient = MongoClients.create(DATABASE.game.connectionUri); diff --git a/src/main/java/emu/grasscutter/game/drop/DropSystemLegacy.java b/src/main/java/emu/grasscutter/game/drop/DropSystemLegacy.java index 2c05e9ef5..d63ad2d99 100644 --- a/src/main/java/emu/grasscutter/game/drop/DropSystemLegacy.java +++ b/src/main/java/emu/grasscutter/game/drop/DropSystemLegacy.java @@ -3,6 +3,7 @@ package emu.grasscutter.game.drop; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.EntityMonster; @@ -26,7 +27,8 @@ public class DropSystemLegacy extends BaseGameSystem { public DropSystemLegacy(GameServer server) { super(server); this.dropData = new Int2ObjectOpenHashMap<>(); - this.load(); + + ResourceLoader.runAsync(this::load); } public Int2ObjectMap> getDropData() { diff --git a/src/main/java/emu/grasscutter/game/expedition/ExpeditionSystem.java b/src/main/java/emu/grasscutter/game/expedition/ExpeditionSystem.java index 4e33d36a2..374c00277 100644 --- a/src/main/java/emu/grasscutter/game/expedition/ExpeditionSystem.java +++ b/src/main/java/emu/grasscutter/game/expedition/ExpeditionSystem.java @@ -2,6 +2,7 @@ package emu.grasscutter.game.expedition; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.DataLoader; +import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.server.game.BaseGameSystem; import emu.grasscutter.server.game.GameServer; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -13,8 +14,9 @@ public class ExpeditionSystem extends BaseGameSystem { public ExpeditionSystem(GameServer server) { super(server); + this.expeditionRewardData = new Int2ObjectOpenHashMap<>(); - this.load(); + ResourceLoader.runAsync(this::load); } public Int2ObjectMap> getExpeditionRewardDataList() { diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaSystem.java b/src/main/java/emu/grasscutter/game/gacha/GachaSystem.java index eebd0977d..42293fa93 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaSystem.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaSystem.java @@ -1,11 +1,10 @@ package emu.grasscutter.game.gacha; -import static emu.grasscutter.config.Configuration.GAME_OPTIONS; - import com.sun.nio.file.SensitivityWatchEventModifier; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.database.DatabaseHelper; @@ -31,11 +30,14 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; +import org.greenrobot.eventbus.Subscribe; + import java.nio.file.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadLocalRandom; -import org.greenrobot.eventbus.Subscribe; + +import static emu.grasscutter.config.Configuration.GAME_OPTIONS; public class GachaSystem extends BaseGameSystem { private static final int starglitterId = 221; @@ -45,8 +47,9 @@ public class GachaSystem extends BaseGameSystem { public GachaSystem(GameServer server) { super(server); + this.gachaBanners = new Int2ObjectOpenHashMap<>(); - this.load(); + ResourceLoader.runAsync(this::load); this.startWatcher(server); } diff --git a/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java index a624855ac..d2429b455 100644 --- a/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java +++ b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java @@ -1,7 +1,5 @@ package emu.grasscutter.game.managers.energy; -import static emu.grasscutter.config.Configuration.GAME_OPTIONS; - import com.google.protobuf.InvalidProtocolBufferException; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.DataLoader; @@ -30,10 +28,13 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import lombok.Getter; + import java.util.List; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; -import lombok.Getter; + +import static emu.grasscutter.config.Configuration.GAME_OPTIONS; public class EnergyManager extends BasePlayerManager { private static final Int2ObjectMap> energyDropData = diff --git a/src/main/java/emu/grasscutter/game/shop/ShopSystem.java b/src/main/java/emu/grasscutter/game/shop/ShopSystem.java index 1689fab77..3fb25cc47 100644 --- a/src/main/java/emu/grasscutter/game/shop/ShopSystem.java +++ b/src/main/java/emu/grasscutter/game/shop/ShopSystem.java @@ -1,10 +1,9 @@ package emu.grasscutter.game.shop; -import static emu.grasscutter.config.Configuration.GAME_OPTIONS; - import emu.grasscutter.Grasscutter; import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.excels.ShopGoodsData; import emu.grasscutter.server.game.BaseGameSystem; @@ -12,10 +11,13 @@ import emu.grasscutter.server.game.GameServer; import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + import java.util.ArrayList; import java.util.List; import java.util.Map; +import static emu.grasscutter.config.Configuration.GAME_OPTIONS; + public class ShopSystem extends BaseGameSystem { private static final int REFRESH_HOUR = 4; // In GMT+8 server private static final String TIME_ZONE = "Asia/Shanghai"; // GMT+8 Timezone @@ -24,9 +26,10 @@ public class ShopSystem extends BaseGameSystem { public ShopSystem(GameServer server) { super(server); + this.shopData = new Int2ObjectOpenHashMap<>(); this.shopChestData = new Int2ObjectOpenHashMap<>(); - this.load(); + ResourceLoader.runAsync(this::load); } public static int getShopNextRefreshTime(ShopInfo shopInfo) { diff --git a/src/main/java/emu/grasscutter/game/systems/AnnouncementSystem.java b/src/main/java/emu/grasscutter/game/systems/AnnouncementSystem.java index d75f60a83..c0b0e1aba 100644 --- a/src/main/java/emu/grasscutter/game/systems/AnnouncementSystem.java +++ b/src/main/java/emu/grasscutter/game/systems/AnnouncementSystem.java @@ -2,6 +2,7 @@ package emu.grasscutter.game.systems; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.DataLoader; +import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.world.World; import emu.grasscutter.net.proto.AnnounceDataOuterClass; @@ -10,12 +11,13 @@ import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.packet.send.PacketServerAnnounceNotify; import emu.grasscutter.server.packet.send.PacketServerAnnounceRevokeNotify; import emu.grasscutter.utils.Utils; -import java.util.*; import lombok.AccessLevel; import lombok.Data; import lombok.Getter; import lombok.experimental.FieldDefaults; +import java.util.*; + @Getter public class AnnouncementSystem extends BaseGameSystem { private final Map announceConfigItemMap; @@ -23,7 +25,7 @@ public class AnnouncementSystem extends BaseGameSystem { public AnnouncementSystem(GameServer server) { super(server); this.announceConfigItemMap = new HashMap<>(); - loadConfig(); + ResourceLoader.runAsync(this::loadConfig); } private int loadConfig() { diff --git a/src/main/java/emu/grasscutter/game/tower/TowerSystem.java b/src/main/java/emu/grasscutter/game/tower/TowerSystem.java index 21acba656..ef1ae7bd7 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerSystem.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerSystem.java @@ -3,6 +3,7 @@ package emu.grasscutter.game.tower; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.data.excels.tower.TowerScheduleData; import emu.grasscutter.server.game.BaseGameSystem; import emu.grasscutter.server.game.GameServer; @@ -15,7 +16,8 @@ public class TowerSystem extends BaseGameSystem { public TowerSystem(GameServer server) { super(server); - this.load(); + + ResourceLoader.runAsync(this::load); } public synchronized void load() { diff --git a/src/main/java/emu/grasscutter/game/world/WorldDataSystem.java b/src/main/java/emu/grasscutter/game/world/WorldDataSystem.java index 010122df1..19f53c1e4 100644 --- a/src/main/java/emu/grasscutter/game/world/WorldDataSystem.java +++ b/src/main/java/emu/grasscutter/game/world/WorldDataSystem.java @@ -3,6 +3,7 @@ package emu.grasscutter.game.world; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.data.excels.InvestigationMonsterData; import emu.grasscutter.data.excels.RewardPreviewData; import emu.grasscutter.data.excels.world.WorldLevelData; @@ -15,12 +16,13 @@ import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneMonster; import emu.grasscutter.server.game.BaseGameSystem; import emu.grasscutter.server.game.GameServer; +import org.luaj.vm2.LuaError; + import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -import org.luaj.vm2.LuaError; public class WorldDataSystem extends BaseGameSystem { private final Map chestInteractHandlerMap; // chestType-Handler @@ -31,7 +33,7 @@ public class WorldDataSystem extends BaseGameSystem { this.chestInteractHandlerMap = new HashMap<>(); this.sceneInvestigationGroupMap = new ConcurrentHashMap<>(); - loadChestConfig(); + ResourceLoader.runAsync(this::loadChestConfig); } public synchronized void loadChestConfig() { diff --git a/src/main/java/emu/grasscutter/plugin/PluginManager.java b/src/main/java/emu/grasscutter/plugin/PluginManager.java index 615bea574..9d3de3d6a 100644 --- a/src/main/java/emu/grasscutter/plugin/PluginManager.java +++ b/src/main/java/emu/grasscutter/plugin/PluginManager.java @@ -1,13 +1,15 @@ package emu.grasscutter.plugin; -import static emu.grasscutter.utils.Language.translate; - import emu.grasscutter.Grasscutter; import emu.grasscutter.server.event.Event; import emu.grasscutter.server.event.EventHandler; import emu.grasscutter.server.event.HandlerPriority; import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.JsonUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import javax.annotation.Nullable; import java.io.File; import java.io.FileNotFoundException; import java.io.InputStreamReader; @@ -18,9 +20,8 @@ import java.net.URLClassLoader; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import javax.annotation.Nullable; -import lombok.AllArgsConstructor; -import lombok.Getter; + +import static emu.grasscutter.utils.Language.translate; /** Manages the server's plugins and the event system. */ public final class PluginManager { diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index e4e897f57..a4c699646 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -1,12 +1,9 @@ package emu.grasscutter.server.game; -import static emu.grasscutter.config.Configuration.DISPATCH_INFO; -import static emu.grasscutter.config.Configuration.GAME_INFO; -import static emu.grasscutter.utils.Language.translate; - import emu.grasscutter.GameConstants; import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter.ServerRunMode; +import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.Account; import emu.grasscutter.game.battlepass.BattlePassSystem; @@ -41,17 +38,22 @@ import emu.grasscutter.server.event.internal.ServerStopEvent; import emu.grasscutter.server.event.types.ServerEvent; import emu.grasscutter.server.scheduler.ServerTaskScheduler; import emu.grasscutter.task.TaskMap; +import kcp.highway.ChannelConfig; +import kcp.highway.KcpServer; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; + import java.net.InetSocketAddress; import java.net.URI; import java.time.Instant; import java.time.OffsetDateTime; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import kcp.highway.ChannelConfig; -import kcp.highway.KcpServer; -import lombok.Getter; -import lombok.Setter; -import lombok.SneakyThrows; + +import static emu.grasscutter.config.Configuration.DISPATCH_INFO; +import static emu.grasscutter.config.Configuration.GAME_INFO; +import static emu.grasscutter.utils.Language.translate; @Getter public final class GameServer extends KcpServer { @@ -140,11 +142,14 @@ public final class GameServer extends KcpServer { this.init(GameSessionManager.getListener(), channelConfig, address); - EnergyManager.initialize(); - StaminaManager.initialize(); - CookingManager.initialize(); - CookingCompoundManager.initialize(); - CombineManger.initialize(); + // Load game managers asyncronously. + ResourceLoader.runAsync(() -> { + EnergyManager.initialize(); + StaminaManager.initialize(); + CookingManager.initialize(); + CookingCompoundManager.initialize(); + CombineManger.initialize(); + }); // Game Server base this.address = address; diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index 3d57e7850..e29eb0a3e 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -1,8 +1,5 @@ package emu.grasscutter.tools; -import static emu.grasscutter.utils.FileUtils.getResourcePath; -import static emu.grasscutter.utils.Language.getTextMapKey; - import emu.grasscutter.GameConstants; import emu.grasscutter.Grasscutter; import emu.grasscutter.command.CommandHandler; @@ -20,6 +17,10 @@ import emu.grasscutter.utils.Language; import emu.grasscutter.utils.Language.TextStrings; import it.unimi.dsi.fastutil.ints.Int2IntRBTreeMap; import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.val; + import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -31,9 +32,9 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.LongStream; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.val; + +import static emu.grasscutter.utils.FileUtils.getResourcePath; +import static emu.grasscutter.utils.Language.getTextMapKey; public final class Tools { /** @@ -52,6 +53,10 @@ public final class Tools { * @throws Exception If an error occurs while generating the handbooks. */ public static void createGmHandbooks(boolean message) throws Exception { + // Check if the GM Handbook directory exists. + val handbookDir = new File("GM Handbook"); + if (handbookDir.exists()) return; + val languages = Language.TextStrings.getLanguages(); ResourceLoader.loadAll(); diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index b79110d9f..fe4080eb3 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -1,9 +1,5 @@ package emu.grasscutter.utils; -import static emu.grasscutter.config.Configuration.FALLBACK_LANGUAGE; -import static emu.grasscutter.utils.FileUtils.getCachePath; -import static emu.grasscutter.utils.FileUtils.getResourcePath; - import com.google.gson.JsonElement; import com.google.gson.JsonObject; import emu.grasscutter.Grasscutter; @@ -17,6 +13,8 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import lombok.EqualsAndHashCode; + import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -28,7 +26,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.IntStream; -import lombok.EqualsAndHashCode; + +import static emu.grasscutter.config.Configuration.FALLBACK_LANGUAGE; +import static emu.grasscutter.utils.FileUtils.getCachePath; +import static emu.grasscutter.utils.FileUtils.getResourcePath; public final class Language { private static final Map cachedLanguages = new ConcurrentHashMap<>(); @@ -339,29 +340,31 @@ public final class Language { */ public static void loadTextMaps(boolean bypassCache) { // Check system timestamps on cache and resources - if (!bypassCache) + if (!bypassCache) { try { long cacheModified = Files.getLastModifiedTime(TEXTMAP_CACHE_PATH).toMillis(); - long textmapsModified = - Files.list(getResourcePath("TextMap")) - .filter(path -> path.toString().endsWith(".json")) - .map( - path -> { - try { - return Files.getLastModifiedTime(path).toMillis(); - } catch (Exception ignored) { - Grasscutter.getLogger() - .debug("Exception while checking modified time: ", path); - return Long.MAX_VALUE; // Don't use cache, something has gone wrong - } - }) - .max(Long::compare) - .get(); + + var stream = Files.list(getResourcePath("TextMap")); + var textmapsModified = stream.filter(path -> + path.toString().endsWith(".json")) + .map( + path -> { + try { + return Files.getLastModifiedTime(path).toMillis(); + } catch (Exception ignored) { + Grasscutter.getLogger() + .debug("Exception while checking modified time: {}.", path); + return Long.MAX_VALUE; // Don't use cache, something has gone wrong + } + }) + .max(Long::compare) + .get(); + stream.close(); Grasscutter.getLogger() - .debug( - "Cache modified %d, textmap modified %d" - .formatted(cacheModified, textmapsModified)); + .debug( + "Cache modified %d, textmap modified %d." + .formatted(cacheModified, textmapsModified)); if (textmapsModified < cacheModified) { // Try loading from cache Grasscutter.getLogger().debug("Loading cached 'TextMaps'..."); @@ -369,8 +372,9 @@ public final class Language { return; } } catch (Exception exception) { - Grasscutter.getLogger().error("Error loading textmaps cache: " + exception.toString()); + Grasscutter.getLogger().error("Error loading textmaps cache: " + exception); } + } // Regenerate cache Grasscutter.getLogger().debug("Generating TextMaps cache");