diff --git a/src/main/java/me/wolfyscript/customcrafting/configs/DataSettings.java b/src/main/java/me/wolfyscript/customcrafting/configs/DataSettings.java index 6402fd35..14927792 100644 --- a/src/main/java/me/wolfyscript/customcrafting/configs/DataSettings.java +++ b/src/main/java/me/wolfyscript/customcrafting/configs/DataSettings.java @@ -39,6 +39,7 @@ public class DataSettings { private static final String PRINT_STACKTRACE = "print_stacktrace"; private static final String BUKKIT_VERSION = "bukkit_version"; private static final String CONFIG_VERSION = "version"; + private static final String SYNC = "sync"; private final ConfigurationSection section; @@ -56,6 +57,10 @@ private Pair timeout(ConfigurationSection section, String key) { Objects.requireNonNullElse(TimeUnit.valueOf(section.getString(key + ".unit")), TimeUnit.SECONDS)); } + public boolean sync() { + return section.getBoolean(SYNC, false); + } + public boolean printPending() { return section.getBoolean(PRINT_PENDING); } diff --git a/src/main/java/me/wolfyscript/customcrafting/handlers/LocalStorageLoader.java b/src/main/java/me/wolfyscript/customcrafting/handlers/LocalStorageLoader.java index eb4ae1e3..2a09f8e1 100644 --- a/src/main/java/me/wolfyscript/customcrafting/handlers/LocalStorageLoader.java +++ b/src/main/java/me/wolfyscript/customcrafting/handlers/LocalStorageLoader.java @@ -43,6 +43,7 @@ import me.wolfyscript.utilities.util.NamespacedKey; import me.wolfyscript.utilities.util.Pair; import org.apache.commons.lang3.time.StopWatch; +import org.bukkit.Bukkit; import java.io.File; import java.io.IOException; @@ -146,6 +147,9 @@ public void load() { api.getConsole().info("- - - - [Local Storage] - - - -"); int processors = Math.min(Runtime.getRuntime().availableProcessors(), dataSettings.maxProcessors()); customCrafting.getLogger().info(PREFIX + "Using " + processors + " threads"); + if (dataSettings.sync()) { + customCrafting.getLogger().info(PREFIX + "Loading data synchronously"); + } executor = Executors.newWorkStealingPool(processors); api.getConsole().info(PREFIX + "Looking through data folder..."); String[] dirs = DATA_FOLDER.list(); @@ -416,7 +420,7 @@ private void checkDependenciesAndRegister(CustomRecipe recipe) { /** * Used to load data & cache the loaded, skipped errors & already existing keys. */ - private abstract static class DataLoader { + private abstract class DataLoader { protected final String[] dirs; @@ -426,6 +430,28 @@ private DataLoader(String[] dirs) { protected abstract void load(); + protected void executeTask(Runnable runnable) { + if (dataSettings.sync()) { + if (Bukkit.isPrimaryThread()) { + // This option will cause the task to run on the main thread! Required for plugins like MMOItems + runnable.run(); + } else { + // The LocalStorageLoader was not called from the main thread. + // Not sure what to do in this case... maybe a hacky Future thingy will work + try { + Bukkit.getScheduler().callSyncMethod(customCrafting, () -> { + runnable.run(); + return true; + }).get(dataSettings.timeoutLoading().getKey(), dataSettings.timeoutLoading().getValue()); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + customCrafting.getLogger().log(Level.SEVERE, "Error while loading recipe ", e); + } + } + } else { + executor.execute(runnable); + } + } + } private class NewDataLoader extends DataLoader { @@ -448,7 +474,7 @@ private void loadRecipesInNamespace(String namespace) { if (isValidFile(file.toFile())) return FileVisitResult.CONTINUE; final var namespacedKey = keyFromFile(namespace, relative); if (isReplaceData() || !customCrafting.getRegistries().getRecipes().has(namespacedKey)) { - executor.execute(() -> { + executeTask(() -> { try { var injectableValues = new InjectableValues.Std(); injectableValues.addValue("key", namespacedKey); @@ -533,7 +559,7 @@ protected void loadOldOrLegacyRecipeFiles(RecipeLoader loader, List fil if (isValidFile(file)) continue; var namespacedKey = new NamespacedKey(customCrafting, namespace + "/" + name.substring(0, name.lastIndexOf("."))); if (!customCrafting.getRegistries().getRecipes().has(namespacedKey)) { - executor.execute(() -> { + executeTask(() -> { try { CustomRecipe recipe = loader.getInstance(namespacedKey, objectMapper.readTree(file)); checkDependenciesAndRegister(recipe); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index dce20a70..82bd004c 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -80,6 +80,9 @@ data: # Specifies the maximum number of processors to use for loading the data # Uses either all available processors, or the specified count, whichever is smaller max_processors: 64 + # When set true, uses the sync scheduler to load the items & recipes. + # Data is then loaded sequentially and not parallel across multiple cores (ignores 'max_processors') + sync: false # Specifies if it should print details about why the recipes are pending. # Recipes are mostly pending because of dependencies, and are validated once a dependency is done loading its data. # Usually not required as pending recipes not validated are marked as 'invalid' after a given timeout (see 'data.timeout.pending').