Revert Multi-threaded resource loading

This commit is contained in:
KingRainbow44 2023-05-20 02:55:33 -04:00
parent 1a6fa43367
commit cad6e90c90
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
13 changed files with 117 additions and 233 deletions

View File

@ -125,7 +125,7 @@ public final class Grasscutter {
} }
// Initialize database. // Initialize database.
DatabaseManager.initializeAsync(); DatabaseManager.initialize();
// Initialize the default systems. // Initialize the default systems.
authenticationSystem = new DefaultAuthentication(); authenticationSystem = new DefaultAuthentication();

View File

@ -25,7 +25,6 @@ import emu.grasscutter.scripts.ScriptLoader;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.JsonUtils; import emu.grasscutter.utils.JsonUtils;
import emu.grasscutter.utils.TsvUtils; import emu.grasscutter.utils.TsvUtils;
import emu.grasscutter.utils.lang.Language;
import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
@ -42,7 +41,6 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -100,151 +98,87 @@ public final class ResourceLoader {
return List.copyOf(map.values()); 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<Boolean> 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<CompletableFuture<Boolean>> futures) {
futures.forEach(CompletableFuture::join);
}
@SneakyThrows @SneakyThrows
public static void loadAll() { public static void loadAll() {
if (loadedAll) return; if (loadedAll) return;
loadedAll = true;
Grasscutter.getLogger().info(translate("messages.status.resources.loading")); Grasscutter.getLogger().info(translate("messages.status.resources.loading"));
// Mark the starting time.
var startTime = System.nanoTime();
// Initialize the script loader. // Initialize the script loader.
ScriptLoader.init(); ScriptLoader.init();
// Load 'TextMaps'. loadConfigData();
var textMaps = ResourceLoader.runAsync(Language::loadTextMaps); // Load ability lists
// Load 'BinOutput'. loadAbilityEmbryos();
var binOutput = ResourceLoader.loadConfigData(); loadOpenConfig();
// Load ability lists. loadAbilityModifiers();
var abilities = // Load resources
ResourceLoader.runAsync( loadResources(true);
() -> { // Process into depots
ResourceLoader.loadAbilityEmbryos(); GameDepot.load();
ResourceLoader.loadOpenConfig(); // Load spawn data and quests
ResourceLoader.loadAbilityModifiers(); loadSpawnData();
}); loadQuests();
// Load 'ExcelBinOutput'. loadScriptSceneData();
var errors = new ConcurrentLinkedQueue<Pair<String, Exception>>(); // Load scene points - must be done AFTER resources are loaded
var excelBinOutput = ResourceLoader.loadResources(true, errors); loadScenePoints();
// Load spawn data and quests.
var scene =
ResourceLoader.runAsync(
() -> {
ResourceLoader.loadSpawnData();
ResourceLoader.loadQuests();
ResourceLoader.loadScriptSceneData();
});
// Load default home layout // Load default home layout
var entities = loadHomeworldDefaultSaveData();
ResourceLoader.runAsync( loadNpcBornData();
() -> { loadBlossomResources();
ResourceLoader.loadHomeworldDefaultSaveData(); cacheTalentLevelSets();
ResourceLoader.loadNpcBornData();
ResourceLoader.loadBlossomResources();
ResourceLoader.cacheTalentLevelSets();
});
// Load custom server resources. // Load custom server resources.
var customServer = loadConfigLevelEntityData();
ResourceLoader.runAsync( loadQuestShareConfig();
() -> { loadGadgetMappings();
ResourceLoader.loadConfigLevelEntityData(); loadActivityCondGroups();
ResourceLoader.loadQuestShareConfig(); loadGroupReplacements();
ResourceLoader.loadGadgetMappings(); loadTrialAvatarCustomData();
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")); Grasscutter.getLogger().info(translate("messages.status.resources.finish"));
loadedAll = true;
} }
public static void loadResources() { public static void loadResources() {
loadResources(false, new ConcurrentLinkedQueue<>()); loadResources(false);
} }
/** public static void loadResources(boolean doReload) {
* Loads all resources from annotated classes. long startTime = System.nanoTime();
* val errors =
* @param doReload Whether to reload resources. new ConcurrentLinkedQueue<
*/ Pair<String, Exception>>(); // Logger in a parallel stream will deadlock
public static List<CompletableFuture<Boolean>> loadResources(
boolean doReload, Queue<Pair<String, Exception>> 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;
var map = GameData.getMapByResourceDef(c); getResourceDefClassesPrioritySets()
if (map == null) return null; .forEach(
classes -> {
classes.stream()
.parallel()
.unordered()
.forEach(
c -> {
val type = c.getAnnotation(ResourceType.class);
if (type == null) return;
return ResourceLoader.runAsync( val map = GameData.getMapByResourceDef(c);
() -> { if (map == null) return;
try {
loadFromResource(c, type, map, doReload); try {
} catch (Exception e) { loadFromResource(c, type, map, doReload);
errors.add(Pair.of(Arrays.toString(type.name()), e)); } catch (Exception e) {
} errors.add(Pair.of(Arrays.toString(type.name()), e));
}); }
}) });
.toList()) });
.flatMap(Collection::stream) errors.forEach(
.toList(); 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");
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@ -292,38 +226,38 @@ public final class ResourceLoader {
private static void loadScenePoints() { private static void loadScenePoints() {
val pattern = Pattern.compile("scene([0-9]+)_point\\.json"); val pattern = Pattern.compile("scene([0-9]+)_point\\.json");
try (var stream = try {
Files.newDirectoryStream(getResourcePath("BinOutput/Scene/Point"), "scene*_point.json")) { Files.newDirectoryStream(getResourcePath("BinOutput/Scene/Point"), "scene*_point.json")
stream.forEach( .forEach(
path -> { path -> {
var matcher = pattern.matcher(path.getFileName().toString()); val matcher = pattern.matcher(path.getFileName().toString());
if (!matcher.find()) return; if (!matcher.find()) return;
var sceneId = Integer.parseInt(matcher.group(1)); int sceneId = Integer.parseInt(matcher.group(1));
ScenePointConfig config; ScenePointConfig config;
try { try {
config = JsonUtils.loadToClass(path, ScenePointConfig.class); config = JsonUtils.loadToClass(path, ScenePointConfig.class);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return; return;
} }
if (config.points == null) return; if (config.points == null) return;
var scenePoints = new IntArrayList(); val scenePoints = new IntArrayList();
config.points.forEach( config.points.forEach(
(pointId, pointData) -> { (pointId, pointData) -> {
var scenePoint = new ScenePointEntry(sceneId, pointData); val scenePoint = new ScenePointEntry(sceneId, pointData);
scenePoints.add((int) pointId); scenePoints.add((int) pointId);
pointData.setId(pointId); pointData.setId(pointId);
GameData.getScenePointIdList().add((int) pointId); GameData.getScenePointIdList().add((int) pointId);
GameData.getScenePointEntryMap().put((sceneId << 16) + pointId, scenePoint); GameData.getScenePointEntryMap().put((sceneId << 16) + pointId, scenePoint);
pointData.updateDailyDungeon(); pointData.updateDailyDungeon();
}); });
GameData.getScenePointsPerScene().put(sceneId, scenePoints); GameData.getScenePointsPerScene().put(sceneId, scenePoints);
}); });
} catch (IOException ignored) { } catch (IOException ignored) {
Grasscutter.getLogger() Grasscutter.getLogger()
.error("Scene point files cannot be found, you cannot use teleport waypoints!"); .error("Scene point files cannot be found, you cannot use teleport waypoints!");
@ -674,34 +608,12 @@ public final class ResourceLoader {
} }
} }
/** Loads data from parsed files. */ private static void loadConfigData() {
private static List<CompletableFuture<Boolean>> loadConfigData() { loadConfigData(GameData.getAvatarConfigData(), "BinOutput/Avatar/", ConfigEntityAvatar.class);
var tasks = new ArrayList<CompletableFuture<Boolean>>(); loadConfigData(
GameData.getMonsterConfigData(), "BinOutput/Monster/", ConfigEntityMonster.class);
// Load config data. loadConfigDataMap(
tasks.add( GameData.getGadgetConfigData(), "BinOutput/Gadget/", ConfigEntityGadget.class);
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 <T extends ConfigEntityBase> void loadConfigData( private static <T extends ConfigEntityBase> void loadConfigData(

View File

@ -35,12 +35,6 @@ public final class DatabaseManager {
return getGameDatastore().getDatabase(); 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() { public static void initialize() {
// Initialize // Initialize
MongoClient gameMongoClient = MongoClients.create(DATABASE.game.connectionUri); MongoClient gameMongoClient = MongoClients.create(DATABASE.game.connectionUri);

View File

@ -3,7 +3,6 @@ package emu.grasscutter.game.drop;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.EntityMonster;
@ -27,8 +26,7 @@ public class DropSystemLegacy extends BaseGameSystem {
public DropSystemLegacy(GameServer server) { public DropSystemLegacy(GameServer server) {
super(server); super(server);
this.dropData = new Int2ObjectOpenHashMap<>(); this.dropData = new Int2ObjectOpenHashMap<>();
this.load();
ResourceLoader.runAsync(this::load);
} }
public Int2ObjectMap<List<DropData>> getDropData() { public Int2ObjectMap<List<DropData>> getDropData() {

View File

@ -2,7 +2,6 @@ package emu.grasscutter.game.expedition;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.server.game.BaseGameSystem; import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@ -14,9 +13,8 @@ public class ExpeditionSystem extends BaseGameSystem {
public ExpeditionSystem(GameServer server) { public ExpeditionSystem(GameServer server) {
super(server); super(server);
this.expeditionRewardData = new Int2ObjectOpenHashMap<>(); this.expeditionRewardData = new Int2ObjectOpenHashMap<>();
ResourceLoader.runAsync(this::load); this.load();
} }
public Int2ObjectMap<List<ExpeditionRewardDataList>> getExpeditionRewardDataList() { public Int2ObjectMap<List<ExpeditionRewardDataList>> getExpeditionRewardDataList() {

View File

@ -6,7 +6,6 @@ import com.sun.nio.file.SensitivityWatchEventModifier;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
@ -46,9 +45,8 @@ public class GachaSystem extends BaseGameSystem {
public GachaSystem(GameServer server) { public GachaSystem(GameServer server) {
super(server); super(server);
this.gachaBanners = new Int2ObjectOpenHashMap<>(); this.gachaBanners = new Int2ObjectOpenHashMap<>();
ResourceLoader.runAsync(this::load); this.load();
this.startWatcher(server); this.startWatcher(server);
} }

View File

@ -5,7 +5,6 @@ import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ShopGoodsData; import emu.grasscutter.data.excels.ShopGoodsData;
import emu.grasscutter.server.game.BaseGameSystem; import emu.grasscutter.server.game.BaseGameSystem;
@ -25,10 +24,9 @@ public class ShopSystem extends BaseGameSystem {
public ShopSystem(GameServer server) { public ShopSystem(GameServer server) {
super(server); super(server);
this.shopData = new Int2ObjectOpenHashMap<>(); this.shopData = new Int2ObjectOpenHashMap<>();
this.shopChestData = new Int2ObjectOpenHashMap<>(); this.shopChestData = new Int2ObjectOpenHashMap<>();
ResourceLoader.runAsync(this::load); this.load();
} }
public static int getShopNextRefreshTime(ShopInfo shopInfo) { public static int getShopNextRefreshTime(ShopInfo shopInfo) {

View File

@ -2,7 +2,6 @@ package emu.grasscutter.game.systems;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.World; import emu.grasscutter.game.world.World;
import emu.grasscutter.net.proto.AnnounceDataOuterClass; import emu.grasscutter.net.proto.AnnounceDataOuterClass;
@ -24,7 +23,7 @@ public class AnnouncementSystem extends BaseGameSystem {
public AnnouncementSystem(GameServer server) { public AnnouncementSystem(GameServer server) {
super(server); super(server);
this.announceConfigItemMap = new HashMap<>(); this.announceConfigItemMap = new HashMap<>();
ResourceLoader.runAsync(this::loadConfig); loadConfig();
} }
private int loadConfig() { private int loadConfig() {

View File

@ -3,7 +3,6 @@ package emu.grasscutter.game.tower;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.data.excels.tower.TowerScheduleData; import emu.grasscutter.data.excels.tower.TowerScheduleData;
import emu.grasscutter.server.game.BaseGameSystem; import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
@ -16,8 +15,7 @@ public class TowerSystem extends BaseGameSystem {
public TowerSystem(GameServer server) { public TowerSystem(GameServer server) {
super(server); super(server);
this.load();
ResourceLoader.runAsync(this::load);
} }
public synchronized void load() { public synchronized void load() {

View File

@ -3,7 +3,6 @@ package emu.grasscutter.game.world;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.data.excels.InvestigationMonsterData; import emu.grasscutter.data.excels.InvestigationMonsterData;
import emu.grasscutter.data.excels.RewardPreviewData; import emu.grasscutter.data.excels.RewardPreviewData;
import emu.grasscutter.data.excels.world.WorldLevelData; import emu.grasscutter.data.excels.world.WorldLevelData;
@ -32,7 +31,7 @@ public class WorldDataSystem extends BaseGameSystem {
this.chestInteractHandlerMap = new HashMap<>(); this.chestInteractHandlerMap = new HashMap<>();
this.sceneInvestigationGroupMap = new ConcurrentHashMap<>(); this.sceneInvestigationGroupMap = new ConcurrentHashMap<>();
ResourceLoader.runAsync(this::loadChestConfig); loadChestConfig();
} }
public synchronized void loadChestConfig() { public synchronized void loadChestConfig() {

View File

@ -1,15 +1,13 @@
package emu.grasscutter.plugin; package emu.grasscutter.plugin;
import static emu.grasscutter.utils.lang.Language.translate;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.event.Event; import emu.grasscutter.server.event.Event;
import emu.grasscutter.server.event.EventHandler; import emu.grasscutter.server.event.EventHandler;
import emu.grasscutter.server.event.HandlerPriority; import emu.grasscutter.server.event.HandlerPriority;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.JsonUtils; import emu.grasscutter.utils.JsonUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import javax.annotation.Nullable;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -20,8 +18,9 @@ import java.net.URLClassLoader;
import java.util.*; import java.util.*;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import javax.annotation.Nullable;
import static emu.grasscutter.utils.lang.Language.translate; import lombok.AllArgsConstructor;
import lombok.Getter;
/** Manages the server's plugins and the event system. */ /** Manages the server's plugins and the event system. */
public final class PluginManager { public final class PluginManager {

View File

@ -3,7 +3,6 @@ package emu.grasscutter.server.game;
import emu.grasscutter.GameConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerRunMode; import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.battlepass.BattlePassSystem; import emu.grasscutter.game.battlepass.BattlePassSystem;
@ -143,15 +142,11 @@ public final class GameServer extends KcpServer implements Iterable<Player> {
this.init(GameSessionManager.getListener(), channelConfig, address); this.init(GameSessionManager.getListener(), channelConfig, address);
// Load game managers asyncronously. EnergyManager.initialize();
ResourceLoader.runAsync( StaminaManager.initialize();
() -> { CookingManager.initialize();
EnergyManager.initialize(); CookingCompoundManager.initialize();
StaminaManager.initialize(); CombineManger.initialize();
CookingManager.initialize();
CookingCompoundManager.initialize();
CombineManger.initialize();
});
// Game Server base // Game Server base
this.address = address; this.address = address;

View File

@ -342,13 +342,11 @@ public final class Language {
*/ */
public static void loadTextMaps(boolean bypassCache) { public static void loadTextMaps(boolean bypassCache) {
// Check system timestamps on cache and resources // Check system timestamps on cache and resources
if (!bypassCache) { if (!bypassCache)
try { try {
long cacheModified = Files.getLastModifiedTime(TEXTMAP_CACHE_PATH).toMillis(); long cacheModified = Files.getLastModifiedTime(TEXTMAP_CACHE_PATH).toMillis();
long textmapsModified =
var stream = Files.list(getResourcePath("TextMap")); Files.list(getResourcePath("TextMap"))
var textmapsModified =
stream
.filter(path -> path.toString().endsWith(".json")) .filter(path -> path.toString().endsWith(".json"))
.map( .map(
path -> { path -> {
@ -356,17 +354,16 @@ public final class Language {
return Files.getLastModifiedTime(path).toMillis(); return Files.getLastModifiedTime(path).toMillis();
} catch (Exception ignored) { } catch (Exception ignored) {
Grasscutter.getLogger() Grasscutter.getLogger()
.debug("Exception while checking modified time: {}.", path); .debug("Exception while checking modified time: ", path);
return Long.MAX_VALUE; // Don't use cache, something has gone wrong return Long.MAX_VALUE; // Don't use cache, something has gone wrong
} }
}) })
.max(Long::compare) .max(Long::compare)
.get(); .get();
stream.close();
Grasscutter.getLogger() Grasscutter.getLogger()
.debug( .debug(
"Cache modified %d, textmap modified %d." "Cache modified %d, textmap modified %d"
.formatted(cacheModified, textmapsModified)); .formatted(cacheModified, textmapsModified));
if (textmapsModified < cacheModified) { if (textmapsModified < cacheModified) {
// Try loading from cache // Try loading from cache
@ -375,9 +372,8 @@ public final class Language {
return; return;
} }
} catch (Exception exception) { } catch (Exception exception) {
Grasscutter.getLogger().error("Error loading textmaps cache: " + exception); Grasscutter.getLogger().error("Error loading textmaps cache: " + exception.toString());
} }
}
// Regenerate cache // Regenerate cache
Grasscutter.getLogger().debug("Generating TextMaps cache"); Grasscutter.getLogger().debug("Generating TextMaps cache");