mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-25 18:09:17 +00:00
Cooking Tutorial
1. Wet the drys 2. Dry the wets 3. Wet the drys 4. Dry the wets 5. Wet the drys 6. Now dust the wets
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
// Gale - JettPack - reduce array allocations
|
||||
|
||||
package me.titaniumtown;
|
||||
|
||||
public final class ArrayConstants {
|
||||
|
||||
private ArrayConstants() {}
|
||||
|
||||
public static final Object[] emptyObjectArray = new Object[0];
|
||||
public static final short[] emptyShortArray = new short[0];
|
||||
public static final int[] emptyIntArray = new int[0];
|
||||
public static final int[] zeroSingletonIntArray = new int[]{0};
|
||||
public static final byte[] emptyByteArray = new byte[0];
|
||||
public static final String[] emptyStringArray = new String[0];
|
||||
public static final long[] emptyLongArray = new long[0];
|
||||
public static final org.bukkit.entity.Entity[] emptyBukkitEntityArray = new org.bukkit.entity.Entity[0];
|
||||
public static final net.minecraft.world.entity.Entity[] emptyEntityArray = new net.minecraft.world.entity.Entity[0];
|
||||
//public static final net.minecraft.server.level.ServerLevel[] emptyServerLevelArray = new net.minecraft.server.level.ServerLevel[0];
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// Gale - Lithium - faster chunk serialization
|
||||
|
||||
package net.caffeinemc.mods.lithium.common.world.chunk;
|
||||
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
import net.minecraft.CrashReport;
|
||||
import net.minecraft.CrashReportCategory;
|
||||
import net.minecraft.ReportedException;
|
||||
import net.minecraft.core.IdMap;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.VarInt;
|
||||
import net.minecraft.world.level.chunk.MissingPaletteEntryException;
|
||||
import net.minecraft.world.level.chunk.Palette;
|
||||
import net.minecraft.world.level.chunk.PaletteResize;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static it.unimi.dsi.fastutil.Hash.FAST_LOAD_FACTOR;
|
||||
|
||||
/**
|
||||
* Generally provides better performance over the vanilla {@link net.minecraft.world.level.chunk.HashMapPalette} when calling
|
||||
* {@link LithiumHashPalette#idFor(Object)} through using a faster backing map and reducing pointer chasing.
|
||||
*/
|
||||
public class LithiumHashPalette<T> implements Palette<T> {
|
||||
private static final int ABSENT_VALUE = -1;
|
||||
|
||||
private final IdMap<T> idList;
|
||||
private final PaletteResize<T> resizeHandler;
|
||||
private final int indexBits;
|
||||
|
||||
private final Reference2IntOpenHashMap<T> table;
|
||||
private T[] entries;
|
||||
private int size = 0;
|
||||
|
||||
private LithiumHashPalette(IdMap<T> idList, PaletteResize<T> resizeHandler, int indexBits, T[] entries, Reference2IntOpenHashMap<T> table, int size) {
|
||||
this.idList = idList;
|
||||
this.resizeHandler = resizeHandler;
|
||||
this.indexBits = indexBits;
|
||||
this.entries = entries;
|
||||
this.table = table;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler, List<T> list) {
|
||||
this(idList, bits, resizeHandler);
|
||||
|
||||
for (T t : list) {
|
||||
this.addEntry(t);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler) {
|
||||
this.idList = idList;
|
||||
this.indexBits = bits;
|
||||
this.resizeHandler = resizeHandler;
|
||||
|
||||
int capacity = 1 << bits;
|
||||
|
||||
this.entries = (T[]) new Object[capacity];
|
||||
this.table = new Reference2IntOpenHashMap<>(capacity, FAST_LOAD_FACTOR);
|
||||
this.table.defaultReturnValue(ABSENT_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int idFor(@NotNull T obj) {
|
||||
int id = this.table.getInt(obj);
|
||||
|
||||
if (id == ABSENT_VALUE) {
|
||||
id = this.computeEntry(obj);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean maybeHas(@NotNull Predicate<T> predicate) {
|
||||
for (int i = 0; i < this.size; ++i) {
|
||||
if (predicate.test(this.entries[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int computeEntry(T obj) {
|
||||
int id = this.addEntry(obj);
|
||||
|
||||
if (id >= 1 << this.indexBits) {
|
||||
if (this.resizeHandler == null) {
|
||||
throw new IllegalStateException("Cannot grow");
|
||||
} else {
|
||||
id = this.resizeHandler.onResize(this.indexBits + 1, obj);
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private int addEntry(T obj) {
|
||||
int nextId = this.size;
|
||||
|
||||
if (nextId >= this.entries.length) {
|
||||
this.resize(this.size);
|
||||
}
|
||||
|
||||
this.table.put(obj, nextId);
|
||||
this.entries[nextId] = obj;
|
||||
|
||||
this.size++;
|
||||
|
||||
return nextId;
|
||||
}
|
||||
|
||||
private void resize(int neededCapacity) {
|
||||
this.entries = Arrays.copyOf(this.entries, HashCommon.nextPowerOfTwo(neededCapacity + 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull T valueFor(int id) {
|
||||
T[] entries = this.entries;
|
||||
|
||||
T entry = null;
|
||||
if (id >= 0 && id < entries.length) {
|
||||
entry = entries[id];
|
||||
}
|
||||
|
||||
if (entry != null) {
|
||||
return entry;
|
||||
} else {
|
||||
throw this.missingPaletteEntryCrash(id);
|
||||
}
|
||||
}
|
||||
|
||||
private ReportedException missingPaletteEntryCrash(int id) {
|
||||
try {
|
||||
throw new MissingPaletteEntryException(id);
|
||||
} catch (MissingPaletteEntryException e) {
|
||||
CrashReport crashReport = CrashReport.forThrowable(e, "[Lithium] Getting Palette Entry");
|
||||
CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk section");
|
||||
crashReportCategory.setDetail("IndexBits", this.indexBits);
|
||||
crashReportCategory.setDetail("Entries", this.entries.length + " Elements: " + Arrays.toString(this.entries));
|
||||
crashReportCategory.setDetail("Table", this.table.size() + " Elements: " + this.table);
|
||||
return new ReportedException(crashReport);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(FriendlyByteBuf buf) {
|
||||
this.clear();
|
||||
|
||||
int entryCount = buf.readVarInt();
|
||||
|
||||
for (int i = 0; i < entryCount; ++i) {
|
||||
this.addEntry(this.idList.byIdOrThrow(buf.readVarInt()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
int size = this.size;
|
||||
buf.writeVarInt(size);
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
buf.writeVarInt(this.idList.getId(this.valueFor(i)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSerializedSize() {
|
||||
int size = VarInt.getByteSize(this.size);
|
||||
|
||||
for (int i = 0; i < this.size; ++i) {
|
||||
size += VarInt.getByteSize(this.idList.getId(this.valueFor(i)));
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Palette<T> copy(@NotNull PaletteResize<T> resizeHandler) {
|
||||
return new LithiumHashPalette<>(this.idList, resizeHandler, this.indexBits, this.entries.clone(), this.table.clone(), this.size);
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
Arrays.fill(this.entries, null);
|
||||
this.table.clear();
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
public List<T> getElements() {
|
||||
T[] copy = Arrays.copyOf(this.entries, this.size);
|
||||
return Arrays.asList(copy);
|
||||
}
|
||||
|
||||
public static <A> Palette<A> create(int bits, IdMap<A> idList, PaletteResize<A> listener, List<A> list) {
|
||||
return new LithiumHashPalette<>(idList, bits, listener, list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// Gale - Gale commands - /gale command
|
||||
|
||||
package org.galemc.gale.command;
|
||||
|
||||
import io.papermc.paper.command.CommandUtil;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import net.minecraft.Util;
|
||||
import org.galemc.gale.command.subcommands.InfoCommand;
|
||||
import org.galemc.gale.command.subcommands.ReloadCommand;
|
||||
import org.galemc.gale.command.subcommands.VersionCommand;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.kyori.adventure.text.Component.newline;
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
|
||||
import static net.kyori.adventure.text.format.NamedTextColor.RED;
|
||||
|
||||
public final class GaleCommand extends Command {
|
||||
public static final String COMMAND_LABEL = "gale";
|
||||
public static final String BASE_PERM = GaleCommands.COMMAND_BASE_PERM + "." + COMMAND_LABEL;
|
||||
private static final Permission basePermission = new Permission(BASE_PERM, PermissionDefault.TRUE);
|
||||
// subcommand label -> subcommand
|
||||
private static final GaleSubcommand RELOAD_SUBCOMMAND = new ReloadCommand();
|
||||
private static final GaleSubcommand VERSION_SUBCOMMAND = new VersionCommand();
|
||||
private static final GaleSubcommand INFO_SUBCOMMAND = new InfoCommand();
|
||||
private static final Map<String, GaleSubcommand> SUBCOMMANDS = Util.make(() -> {
|
||||
final Map<Set<String>, GaleSubcommand> commands = new HashMap<>();
|
||||
|
||||
commands.put(Set.of(ReloadCommand.LITERAL_ARGUMENT), RELOAD_SUBCOMMAND);
|
||||
commands.put(Set.of(VersionCommand.LITERAL_ARGUMENT), VERSION_SUBCOMMAND);
|
||||
commands.put(Set.of(InfoCommand.LITERAL_ARGUMENT), INFO_SUBCOMMAND);
|
||||
|
||||
return commands.entrySet().stream()
|
||||
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
});
|
||||
// alias -> subcommand label
|
||||
private static final Map<String, String> ALIASES = Util.make(() -> {
|
||||
final Map<String, Set<String>> aliases = new HashMap<>();
|
||||
|
||||
aliases.put(VersionCommand.LITERAL_ARGUMENT, Set.of("ver"));
|
||||
aliases.put(InfoCommand.LITERAL_ARGUMENT, Set.of("about"));
|
||||
|
||||
return aliases.entrySet().stream()
|
||||
.flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey())))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
});
|
||||
|
||||
private String createUsageMessage(Collection<String> arguments) {
|
||||
return "/" + COMMAND_LABEL + " [" + String.join(" | ", arguments) + "]";
|
||||
}
|
||||
|
||||
public GaleCommand() {
|
||||
super(COMMAND_LABEL);
|
||||
this.description = "Gale related commands";
|
||||
this.usageMessage = this.createUsageMessage(SUBCOMMANDS.keySet());
|
||||
final List<Permission> permissions = SUBCOMMANDS.values().stream().map(GaleSubcommand::getPermission).filter(Objects::nonNull).toList();
|
||||
this.setPermission(BASE_PERM);
|
||||
final PluginManager pluginManager = Bukkit.getServer().getPluginManager();
|
||||
pluginManager.addPermission(basePermission);
|
||||
for (final Permission permission : permissions) {
|
||||
pluginManager.addPermission(permission);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabComplete(
|
||||
final CommandSender sender,
|
||||
final String alias,
|
||||
final String[] args,
|
||||
final @Nullable Location location
|
||||
) throws IllegalArgumentException {
|
||||
if (args.length <= 1) {
|
||||
List<String> subCommandArguments = new ArrayList<>(SUBCOMMANDS.size());
|
||||
for (Map.Entry<String, GaleSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
|
||||
if (subCommandEntry.getValue().testPermission(sender)) {
|
||||
subCommandArguments.add(subCommandEntry.getKey());
|
||||
}
|
||||
}
|
||||
return CommandUtil.getListMatchingLast(sender, args, subCommandArguments);
|
||||
}
|
||||
|
||||
final @Nullable Pair<String, GaleSubcommand> subCommand = resolveCommand(args[0]);
|
||||
if (subCommand != null && subCommand.second().testPermission(sender)) {
|
||||
return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length));
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private boolean testHasOnePermission(CommandSender sender) {
|
||||
for (Map.Entry<String, GaleSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
|
||||
if (subCommandEntry.getValue().testPermission(sender)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(
|
||||
final CommandSender sender,
|
||||
final String commandLabel,
|
||||
final String[] args
|
||||
) {
|
||||
|
||||
// Check if the sender has the base permission and at least one specific permission
|
||||
if (!sender.hasPermission(basePermission) || !this.testHasOnePermission(sender)) {
|
||||
sender.sendMessage(Bukkit.permissionMessage());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Determine the usage message with the subcommands they can perform
|
||||
List<String> subCommandArguments = new ArrayList<>(SUBCOMMANDS.size());
|
||||
for (Map.Entry<String, GaleSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
|
||||
if (subCommandEntry.getValue().testPermission(sender)) {
|
||||
subCommandArguments.add(subCommandEntry.getKey());
|
||||
}
|
||||
}
|
||||
String specificUsageMessage = this.createUsageMessage(subCommandArguments);
|
||||
|
||||
// If they did not give a subcommand
|
||||
if (args.length == 0) {
|
||||
INFO_SUBCOMMAND.execute(sender, InfoCommand.LITERAL_ARGUMENT, me.titaniumtown.ArrayConstants.emptyStringArray); // Gale - JettPack - reduce array allocations
|
||||
sender.sendMessage(newline().append(text("Command usage: " + specificUsageMessage, GRAY)));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If they do not have permission for the subcommand they gave, or the argument is not a valid subcommand
|
||||
final @Nullable Pair<String, GaleSubcommand> subCommand = resolveCommand(args[0]);
|
||||
if (subCommand == null || !subCommand.second().testPermission(sender)) {
|
||||
sender.sendMessage(text("Usage: " + specificUsageMessage, RED));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute the subcommand
|
||||
final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
return subCommand.second().execute(sender, subCommand.first(), choppedArgs);
|
||||
|
||||
}
|
||||
|
||||
private static @Nullable Pair<String, GaleSubcommand> resolveCommand(String label) {
|
||||
label = label.toLowerCase(Locale.ENGLISH);
|
||||
@Nullable GaleSubcommand subCommand = SUBCOMMANDS.get(label);
|
||||
if (subCommand == null) {
|
||||
final @Nullable String command = ALIASES.get(label);
|
||||
if (command != null) {
|
||||
label = command;
|
||||
subCommand = SUBCOMMANDS.get(command);
|
||||
}
|
||||
}
|
||||
|
||||
if (subCommand != null) {
|
||||
return Pair.of(label, subCommand);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Gale - Gale commands
|
||||
|
||||
package org.galemc.gale.command;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class GaleCommands {
|
||||
|
||||
public static final String COMMAND_BASE_PERM = CraftDefaultPermissions.GALE_ROOT + ".command";
|
||||
|
||||
private GaleCommands() {
|
||||
}
|
||||
|
||||
private static final Map<String, Command> COMMANDS = new HashMap<>();
|
||||
|
||||
static {
|
||||
COMMANDS.put(GaleCommand.COMMAND_LABEL, new GaleCommand());
|
||||
}
|
||||
|
||||
public static void registerCommands(final MinecraftServer server) {
|
||||
COMMANDS.forEach((s, command) ->
|
||||
server.server.getCommandMap().register(s, "Gale", command)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Gale - Gale commands
|
||||
|
||||
package org.galemc.gale.command;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.Permission;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public interface GaleSubcommand {
|
||||
|
||||
boolean execute(CommandSender sender, String subCommand, String[] args);
|
||||
|
||||
default List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
boolean testPermission(CommandSender sender);
|
||||
|
||||
@Nullable Permission getPermission();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Gale - Gale commands
|
||||
|
||||
package org.galemc.gale.command;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
|
||||
public abstract class PermissionedGaleSubcommand implements GaleSubcommand {
|
||||
|
||||
public final Permission permission;
|
||||
|
||||
protected PermissionedGaleSubcommand(Permission permission) {
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
protected PermissionedGaleSubcommand(String permission, PermissionDefault permissionDefault) {
|
||||
this(new Permission(permission, permissionDefault));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testPermission(CommandSender sender) {
|
||||
return sender.hasPermission(this.permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Permission getPermission() {
|
||||
return this.permission;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Gale - Gale commands - /gale info command
|
||||
|
||||
package org.galemc.gale.command.subcommands;
|
||||
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.galemc.gale.command.GaleSubcommand;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.Permission;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class InfoCommand implements GaleSubcommand {
|
||||
|
||||
public final static String LITERAL_ARGUMENT = "info";
|
||||
|
||||
@Override
|
||||
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
sender.sendMessage(
|
||||
text("Gale is a performant Minecraft server system. Find us on: ")
|
||||
.append(text("https://github.com/GaleMC/Gale")
|
||||
.decorate(TextDecoration.UNDERLINED)
|
||||
.clickEvent(ClickEvent.openUrl("https://github.com/GaleMC/Gale")))
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testPermission(CommandSender sender) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Permission getPermission() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Gale - Gale commands - /gale reload command
|
||||
|
||||
package org.galemc.gale.command.subcommands;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.galemc.gale.command.GaleCommand;
|
||||
import org.galemc.gale.command.PermissionedGaleSubcommand;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
|
||||
import static net.kyori.adventure.text.format.NamedTextColor.RED;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class ReloadCommand extends PermissionedGaleSubcommand {
|
||||
|
||||
public final static String LITERAL_ARGUMENT = "reload";
|
||||
public static final String PERM = GaleCommand.BASE_PERM + "." + LITERAL_ARGUMENT;
|
||||
|
||||
public ReloadCommand() {
|
||||
super(PERM, PermissionDefault.OP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
this.doReload(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void doReload(final CommandSender sender) {
|
||||
Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED));
|
||||
Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED));
|
||||
|
||||
MinecraftServer server = ((CraftServer) sender.getServer()).getServer();
|
||||
server.galeConfigurations.reloadConfigs(server);
|
||||
server.server.reloadCount++;
|
||||
|
||||
Command.broadcastCommandMessage(sender, text("Gale config reload complete.", GREEN));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Gale - Gale commands - /gale version command
|
||||
|
||||
package org.galemc.gale.command.subcommands;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.galemc.gale.command.GaleCommand;
|
||||
import org.galemc.gale.command.PermissionedGaleSubcommand;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class VersionCommand extends PermissionedGaleSubcommand {
|
||||
|
||||
public final static String LITERAL_ARGUMENT = "version";
|
||||
public static final String PERM = GaleCommand.BASE_PERM + "." + LITERAL_ARGUMENT;
|
||||
|
||||
public VersionCommand() {
|
||||
super(PERM, PermissionDefault.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
final @Nullable Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version");
|
||||
if (ver != null) {
|
||||
ver.execute(sender, GaleCommand.COMMAND_LABEL, me.titaniumtown.ArrayConstants.emptyStringArray); // Gale - JettPack - reduce array allocations
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testPermission(CommandSender sender) {
|
||||
return super.testPermission(sender) && sender.hasPermission("bukkit.command.version");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
// Gale - Gale configuration
|
||||
|
||||
package org.galemc.gale.configuration;
|
||||
|
||||
import com.google.common.collect.Table;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.leangen.geantyref.TypeToken;
|
||||
import io.papermc.paper.configuration.Configuration;
|
||||
import io.papermc.paper.configuration.ConfigurationPart;
|
||||
import io.papermc.paper.configuration.Configurations;
|
||||
import io.papermc.paper.configuration.NestedSetting;
|
||||
import io.papermc.paper.configuration.PaperConfigurations;
|
||||
import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization;
|
||||
import io.papermc.paper.configuration.mapping.InnerClassFieldDiscoverer;
|
||||
import io.papermc.paper.configuration.serializer.ComponentSerializer;
|
||||
import io.papermc.paper.configuration.serializer.EnumValueSerializer;
|
||||
import io.papermc.paper.configuration.serializer.PacketClassSerializer;
|
||||
import io.papermc.paper.configuration.serializer.StringRepresentableSerializer;
|
||||
import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer;
|
||||
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
|
||||
import io.papermc.paper.configuration.serializer.collections.TableSerializer;
|
||||
import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer;
|
||||
import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer;
|
||||
import io.papermc.paper.configuration.transformation.Transformations;
|
||||
import io.papermc.paper.configuration.type.BooleanOrDefault;
|
||||
import io.papermc.paper.configuration.type.Duration;
|
||||
import io.papermc.paper.configuration.type.EngineMode;
|
||||
import io.papermc.paper.configuration.type.fallback.FallbackValueSerializer;
|
||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||
import io.papermc.paper.configuration.type.number.IntOr;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2LongMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static io.leangen.geantyref.GenericTypeReflector.erase;
|
||||
|
||||
@SuppressWarnings("Convert2Diamond")
|
||||
public class GaleConfigurations extends Configurations<GaleGlobalConfiguration, GaleWorldConfiguration> {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
static final String GLOBAL_CONFIG_FILE_NAME = "gale-global.yml";
|
||||
static final String WORLD_DEFAULTS_CONFIG_FILE_NAME = "gale-world-defaults.yml";
|
||||
static final String WORLD_CONFIG_FILE_NAME = "gale-world.yml";
|
||||
public static final String CONFIG_DIR = "config";
|
||||
|
||||
private static final String GLOBAL_HEADER = String.format("""
|
||||
This is the global configuration file for Gale.
|
||||
As you can see, there's a lot to configure. Some options may impact gameplay, so use
|
||||
with caution, and make sure you know what each option does before configuring.
|
||||
|
||||
If you need help with the configuration or have any questions related to Gale,
|
||||
join us in our Discord, or check the GitHub Wiki pages.
|
||||
|
||||
The world configuration options are inside
|
||||
their respective world folder. The files are named %s
|
||||
|
||||
Wiki: https://github.com/GaleMC/Gale/wiki
|
||||
Discord: https://discord.gg/gwezNT8c24""", WORLD_CONFIG_FILE_NAME);
|
||||
|
||||
private static final String WORLD_DEFAULTS_HEADER = """
|
||||
This is the world defaults configuration file for Gale.
|
||||
As you can see, there's a lot to configure. Some options may impact gameplay, so use
|
||||
with caution, and make sure you know what each option does before configuring.
|
||||
|
||||
If you need help with the configuration or have any questions related to Gale,
|
||||
join us in our Discord, or check the GitHub Wiki pages.
|
||||
|
||||
Configuration options here apply to all worlds, unless you specify overrides inside
|
||||
the world-specific config file inside each world folder.
|
||||
|
||||
Wiki: https://github.com/GaleMC/Gale/wiki
|
||||
Discord: https://discord.gg/gwezNT8c24""";
|
||||
|
||||
private static final Function<ContextMap, String> WORLD_HEADER = map -> String.format("""
|
||||
This is a world configuration file for Gale.
|
||||
This file may start empty but can be filled with settings to override ones in the %s/%s
|
||||
|
||||
World: %s (%s)""",
|
||||
CONFIG_DIR,
|
||||
WORLD_DEFAULTS_CONFIG_FILE_NAME,
|
||||
map.require(WORLD_NAME),
|
||||
map.require(WORLD_KEY)
|
||||
);
|
||||
|
||||
private static final String MOVED_NOTICE = """
|
||||
The global and world default configuration files have moved to %s
|
||||
and the world-specific configuration file has been moved inside
|
||||
the respective world folder.
|
||||
|
||||
See https://github.com/GaleMC/Gale/wiki for more information.
|
||||
""";
|
||||
|
||||
public GaleConfigurations(final Path globalFolder) {
|
||||
super(globalFolder, GaleGlobalConfiguration.class, GaleWorldConfiguration.class, GLOBAL_CONFIG_FILE_NAME, WORLD_DEFAULTS_CONFIG_FILE_NAME, WORLD_CONFIG_FILE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected YamlConfigurationLoader.Builder createLoaderBuilder() {
|
||||
return super.createLoaderBuilder()
|
||||
.defaultOptions(GaleConfigurations::defaultOptions);
|
||||
}
|
||||
|
||||
private static ConfigurationOptions defaultOptions(ConfigurationOptions options) {
|
||||
return options.serializers(builder -> builder
|
||||
.register(MapSerializer.TYPE, new MapSerializer(false))
|
||||
.register(new EnumValueSerializer())
|
||||
.register(new ComponentSerializer())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObjectMapper.Factory.Builder createGlobalObjectMapperFactoryBuilder() {
|
||||
return defaultGlobalFactoryBuilder(super.createGlobalObjectMapperFactoryBuilder());
|
||||
}
|
||||
|
||||
private static ObjectMapper.Factory.Builder defaultGlobalFactoryBuilder(ObjectMapper.Factory.Builder builder) {
|
||||
return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(RegistryAccess registryAccess) {
|
||||
return super.createGlobalLoaderBuilder(registryAccess)
|
||||
.defaultOptions((options) -> defaultGlobalOptions(registryAccess, options));
|
||||
}
|
||||
|
||||
private static ConfigurationOptions defaultGlobalOptions(RegistryAccess registryAccess, ConfigurationOptions options) {
|
||||
return options
|
||||
.header(GLOBAL_HEADER)
|
||||
.serializers(builder -> builder.register(new PacketClassSerializer())
|
||||
.register(new RegistryValueSerializer<>(new TypeToken<DataComponentType<?>>() {}, registryAccess, Registries.DATA_COMPONENT_TYPE, false))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaleGlobalConfiguration initializeGlobalConfiguration(final RegistryAccess registryAccess) throws ConfigurateException {
|
||||
GaleGlobalConfiguration configuration = super.initializeGlobalConfiguration(registryAccess);
|
||||
GaleGlobalConfiguration.set(configuration);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContextMap.Builder createDefaultContextMap(final RegistryAccess registryAccess) {
|
||||
return super.createDefaultContextMap(registryAccess)
|
||||
.put(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY, PaperConfigurations.SPIGOT_WORLD_DEFAULTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final ContextMap contextMap) {
|
||||
return super.createWorldObjectMapperFactoryBuilder(contextMap)
|
||||
.addNodeResolver(new RequiresSpigotInitialization.Factory(contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get()))
|
||||
.addNodeResolver(new NestedSetting.Factory())
|
||||
.addDiscoverer(InnerClassFieldDiscoverer.galeWorldConfig(contextMap));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) {
|
||||
final RegistryAccess access = contextMap.require(REGISTRY_ACCESS);
|
||||
return super.createWorldConfigLoaderBuilder(contextMap)
|
||||
.defaultOptions(options -> options
|
||||
.header(contextMap.require(WORLD_NAME).equals(WORLD_DEFAULTS) ? WORLD_DEFAULTS_HEADER : WORLD_HEADER.apply(contextMap))
|
||||
.serializers(serializers -> serializers
|
||||
.register(new TypeToken<Reference2IntMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Reference2IntMap<?>>(Reference2IntOpenHashMap::new, Integer.TYPE))
|
||||
.register(new TypeToken<Reference2LongMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Reference2LongMap<?>>(Reference2LongOpenHashMap::new, Long.TYPE))
|
||||
.register(new TypeToken<Table<?, ?, ?>>() {}, new TableSerializer())
|
||||
.register(new StringRepresentableSerializer())
|
||||
.register(IntOr.Default.SERIALIZER)
|
||||
.register(IntOr.Disabled.SERIALIZER)
|
||||
.register(DoubleOr.Default.SERIALIZER)
|
||||
.register(BooleanOrDefault.SERIALIZER)
|
||||
.register(Duration.SERIALIZER)
|
||||
.register(EngineMode.SERIALIZER)
|
||||
.register(FallbackValueSerializer.create(contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(), MinecraftServer::getServer))
|
||||
.register(new RegistryValueSerializer<>(new TypeToken<EntityType<?>>() {}, access, Registries.ENTITY_TYPE, true))
|
||||
.register(new RegistryValueSerializer<>(Item.class, access, Registries.ITEM, true))
|
||||
.register(new RegistryHolderSerializer<>(new TypeToken<ConfiguredFeature<?, ?>>() {}, access, Registries.CONFIGURED_FEATURE, false))
|
||||
.register(new RegistryHolderSerializer<>(Item.class, access, Registries.ITEM, true))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode node, final @Nullable ConfigurationNode defaultsNode) throws ConfigurateException {
|
||||
final ConfigurationNode version = node.node(Configuration.VERSION_FIELD);
|
||||
final String world = contextMap.require(WORLD_NAME);
|
||||
if (version.virtual()) {
|
||||
LOGGER.warn("The Gale world config file for {} didn't have a version set, assuming latest", world);
|
||||
version.raw(GaleWorldConfiguration.CURRENT_VERSION);
|
||||
}
|
||||
if (GaleRemovedConfigurations.REMOVED_WORLD_PATHS.length > 0) {
|
||||
ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
|
||||
for (NodePath path : GaleRemovedConfigurations.REMOVED_WORLD_PATHS) {
|
||||
builder.addAction(path, TransformAction.remove());
|
||||
}
|
||||
builder.build().apply(node);
|
||||
}
|
||||
// ADD FUTURE TRANSFORMS HERE
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyGlobalConfigTransformations(ConfigurationNode node) throws ConfigurateException {
|
||||
if (GaleRemovedConfigurations.REMOVED_GLOBAL_PATHS.length > 0) {
|
||||
ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
|
||||
for (NodePath path : GaleRemovedConfigurations.REMOVED_GLOBAL_PATHS) {
|
||||
builder.addAction(path, TransformAction.remove());
|
||||
}
|
||||
builder.build().apply(node);
|
||||
}
|
||||
// ADD FUTURE TRANSFORMS HERE
|
||||
}
|
||||
|
||||
private static final List<Transformations.DefaultsAware> DEFAULT_AWARE_TRANSFORMATIONS = Collections.emptyList();
|
||||
|
||||
@Override
|
||||
protected void applyDefaultsAwareWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode worldNode, final ConfigurationNode defaultsNode) throws ConfigurateException {
|
||||
final ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
|
||||
// ADD FUTURE TRANSFORMS HERE (these transforms run after the defaults have been merged into the node)
|
||||
DEFAULT_AWARE_TRANSFORMATIONS.forEach(transform -> transform.apply(builder, contextMap, defaultsNode));
|
||||
|
||||
ConfigurationTransformation transformation;
|
||||
try {
|
||||
transformation = builder.build(); // build throws IAE if no actions were provided (bad zml)
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return;
|
||||
}
|
||||
transformation.apply(worldNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaleWorldConfiguration createWorldConfig(final ContextMap contextMap) {
|
||||
final String levelName = contextMap.require(WORLD_NAME);
|
||||
try {
|
||||
return super.createWorldConfig(contextMap);
|
||||
} catch (IOException exception) {
|
||||
throw new RuntimeException("Could not create Gale world config for " + levelName, exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isConfigType(final Type type) {
|
||||
return ConfigurationPart.class.isAssignableFrom(erase(type));
|
||||
}
|
||||
|
||||
public void reloadConfigs(MinecraftServer server) {
|
||||
try {
|
||||
this.initializeGlobalConfiguration(server.registryAccess(), reloader(this.globalConfigClass, GaleGlobalConfiguration.get()));
|
||||
this.initializeWorldDefaultsConfiguration(server.registryAccess());
|
||||
for (ServerLevel level : server.getAllLevels()) {
|
||||
this.createWorldConfig(PaperConfigurations.createWorldContextMap(level), reloader(this.worldConfigClass, level.galeConfig()));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Could not reload Gale configuration files", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static GaleConfigurations setup(final Path configDir) throws Exception {
|
||||
try {
|
||||
PaperConfigurations.createDirectoriesSymlinkAware(configDir);
|
||||
return new GaleConfigurations(configDir);
|
||||
} catch (final IOException ex) {
|
||||
throw new RuntimeException("Could not setup GaleConfigurations", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int globalConfigVersion() {
|
||||
return GaleGlobalConfiguration.CURRENT_VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int worldConfigVersion() {
|
||||
return getWorldConfigurationCurrentVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldConfigurationCurrentVersion() {
|
||||
return GaleWorldConfiguration.CURRENT_VERSION;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// Gale - Gale configuration
|
||||
|
||||
package org.galemc.gale.configuration;
|
||||
|
||||
import io.papermc.paper.configuration.Configuration;
|
||||
import io.papermc.paper.configuration.ConfigurationPart;
|
||||
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
|
||||
import org.spongepowered.configurate.objectmapping.meta.PostProcess;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
import org.bukkit.plugin.java.JavaPluginLoader;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "InnerClassMayBeStatic"})
|
||||
public class GaleGlobalConfiguration extends ConfigurationPart {
|
||||
static final int CURRENT_VERSION = 1;
|
||||
private static GaleGlobalConfiguration instance;
|
||||
|
||||
public static GaleGlobalConfiguration get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void set(GaleGlobalConfiguration instance) {
|
||||
GaleGlobalConfiguration.instance = instance;
|
||||
}
|
||||
|
||||
@Setting(Configuration.VERSION_FIELD)
|
||||
public int version = CURRENT_VERSION;
|
||||
|
||||
public SmallOptimizations smallOptimizations;
|
||||
|
||||
public class SmallOptimizations extends ConfigurationPart {
|
||||
|
||||
public ReducedIntervals reducedIntervals;
|
||||
|
||||
public class ReducedIntervals extends ConfigurationPart {
|
||||
|
||||
public int increaseTimeStatistics = 20; // Gale - Hydrinity - increase time statistics in intervals
|
||||
public int updateEntityLineOfSight = 4; // Gale - Petal - reduce line of sight updates
|
||||
|
||||
@PostProcess
|
||||
public void postProcess() {
|
||||
net.minecraft.world.entity.player.Player.increaseTimeStatisticsInterval = Math.max(1, increaseTimeStatistics); // Gale - Hydrinity - increase time statistics in intervals - store as static field for fast access
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public GameplayMechanics gameplayMechanics;
|
||||
|
||||
public class GameplayMechanics extends ConfigurationPart {
|
||||
|
||||
public boolean enableBookWriting = true; // Gale - Pufferfish - make book writing configurable
|
||||
|
||||
}
|
||||
|
||||
public Misc misc;
|
||||
|
||||
public class Misc extends ConfigurationPart {
|
||||
|
||||
public boolean verifyChatOrder = true; // Gale - Pufferfish - make chat order verification configurable
|
||||
public int premiumAccountSlowLoginTimeout = -1; // Gale - make slow login timeout configurable
|
||||
public boolean ignoreNullLegacyStructureData = false; // Gale - MultiPaper - ignore null legacy structure data
|
||||
|
||||
public Keepalive keepalive;
|
||||
|
||||
public class Keepalive extends ConfigurationPart {
|
||||
public boolean sendMultiple = true; // Gale - Purpur - send multiple keep-alive packets
|
||||
}
|
||||
|
||||
// Gale start - YAPFA - last tick time - in TPS command
|
||||
public LastTickTimeInTpsCommand lastTickTimeInTpsCommand;
|
||||
|
||||
public class LastTickTimeInTpsCommand extends ConfigurationPart {
|
||||
public boolean enabled = false;
|
||||
public boolean addOversleep = false;
|
||||
}
|
||||
// Gale end - YAPFA - last tick time - in TPS command
|
||||
|
||||
}
|
||||
|
||||
public LogToConsole logToConsole;
|
||||
|
||||
public class LogToConsole extends ConfigurationPart { // Gale - EMC - softly log invalid pool element errors
|
||||
|
||||
public boolean invalidStatistics = true; // Gale - EMC - do not log invalid statistics
|
||||
public boolean ignoredAdvancements = true; // Gale - Purpur - do not log ignored advancements
|
||||
public boolean setBlockInFarChunk = true; // Gale - Purpur - do not log setBlock in far chunks
|
||||
public boolean unrecognizedRecipes = false; // Gale - Purpur - do not log unrecognized recipes
|
||||
public boolean legacyMaterialInitialization = false; // Gale - Purpur - do not log legacy Material initialization
|
||||
public boolean nullIdDisconnections = true; // Gale - Pufferfish - do not log disconnections with null id
|
||||
public boolean playerLoginLocations = true; // Gale - JettPack - make logging login location configurable
|
||||
|
||||
public Chat chat;
|
||||
|
||||
public class Chat extends ConfigurationPart {
|
||||
public boolean emptyMessageWarning = false; // Gale - do not log empty message warnings
|
||||
public boolean expiredMessageWarning = false; // Gale - do not log expired message warnings
|
||||
public boolean notSecureMarker = true; // Gale - do not log Not Secure marker
|
||||
}
|
||||
|
||||
// Gale start - Purpur - do not log plugin library loads
|
||||
public PluginLibraryLoader pluginLibraryLoader;
|
||||
|
||||
public class PluginLibraryLoader extends ConfigurationPart {
|
||||
|
||||
public boolean downloads = true;
|
||||
public boolean startLoadLibrariesForPlugin = true;
|
||||
public boolean libraryLoaded = true;
|
||||
|
||||
@PostProcess
|
||||
public void postProcess() {
|
||||
JavaPluginLoader.logDownloads = this.downloads;
|
||||
JavaPluginLoader.logStartLoadLibrariesForPlugin = this.startLoadLibrariesForPlugin;
|
||||
JavaPluginLoader.logLibraryLoaded = this.libraryLoaded;
|
||||
}
|
||||
|
||||
}
|
||||
// Gale end - Purpur - do not log plugin library loads
|
||||
|
||||
// Gale start - EMC - softly log invalid pool element errors
|
||||
public String invalidPoolElementErrorLogLevel = "info";
|
||||
public transient Consumer<String> invalidPoolElementErrorStringConsumer;
|
||||
|
||||
@PostProcess
|
||||
public void postProcess() {
|
||||
this.invalidPoolElementErrorStringConsumer = switch (this.invalidPoolElementErrorLogLevel.toLowerCase(Locale.ROOT)) {
|
||||
case "none" -> $ -> {};
|
||||
case "info", "log" -> PoolElementStructurePiece.LOGGER::info;
|
||||
case "warn", "warning" -> PoolElementStructurePiece.LOGGER::warn;
|
||||
default -> PoolElementStructurePiece.LOGGER::error;
|
||||
};
|
||||
}
|
||||
// Gale end - EMC - softly log invalid pool element errors
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Gale - Gale configuration
|
||||
|
||||
package org.galemc.gale.configuration;
|
||||
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
|
||||
interface GaleRemovedConfigurations {
|
||||
|
||||
NodePath[] REMOVED_WORLD_PATHS = {};
|
||||
|
||||
NodePath[] REMOVED_GLOBAL_PATHS = {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// Gale - Gale configuration
|
||||
|
||||
package org.galemc.gale.configuration;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.papermc.paper.configuration.Configuration;
|
||||
import io.papermc.paper.configuration.ConfigurationPart;
|
||||
import io.papermc.paper.configuration.PaperConfigurations;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.slf4j.Logger;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal", "InnerClassMayBeStatic"})
|
||||
public class GaleWorldConfiguration extends ConfigurationPart {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
public static final int CURRENT_VERSION = 1;
|
||||
|
||||
private transient final SpigotWorldConfig spigotConfig;
|
||||
private transient final ResourceLocation worldKey;
|
||||
|
||||
public GaleWorldConfiguration(SpigotWorldConfig spigotConfig, ResourceLocation worldKey) {
|
||||
this.spigotConfig = spigotConfig;
|
||||
this.worldKey = worldKey;
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return this.worldKey.equals(PaperConfigurations.WORLD_DEFAULTS_KEY);
|
||||
}
|
||||
|
||||
@Setting(Configuration.VERSION_FIELD)
|
||||
public int version = CURRENT_VERSION;
|
||||
|
||||
public SmallOptimizations smallOptimizations;
|
||||
|
||||
public class SmallOptimizations extends ConfigurationPart {
|
||||
|
||||
public boolean saveFireworks = true; // Gale - EMC - make saving fireworks configurable
|
||||
public boolean useOptimizedSheepOffspringColor = true; // Gale - carpet-fixes - optimize sheep offspring color
|
||||
|
||||
// Gale start - Airplane - reduce projectile chunk loading
|
||||
public MaxProjectileChunkLoads maxProjectileChunkLoads;
|
||||
|
||||
public class MaxProjectileChunkLoads extends ConfigurationPart {
|
||||
|
||||
public int perTick = 10;
|
||||
|
||||
public PerProjectile perProjectile;
|
||||
|
||||
public class PerProjectile extends ConfigurationPart {
|
||||
public int max = 10;
|
||||
public boolean resetMovementAfterReachLimit = false;
|
||||
public boolean removeFromWorldAfterReachLimit = false;
|
||||
}
|
||||
|
||||
}
|
||||
// Gale end - Airplane - reduce projectile chunk loading
|
||||
|
||||
public ReducedIntervals reducedIntervals;
|
||||
|
||||
public class ReducedIntervals extends ConfigurationPart {
|
||||
|
||||
public int acquirePoiForStuckEntity = 60; // Gale - Airplane - reduce acquire POI for stuck entities
|
||||
public int checkStuckInWall = 10; // Gale - Pufferfish - reduce in wall checks
|
||||
public int villagerItemRepickup = 100; // Gale - EMC - reduce villager item re-pickup
|
||||
|
||||
public CheckNearbyItem checkNearbyItem;
|
||||
|
||||
public class CheckNearbyItem extends ConfigurationPart {
|
||||
|
||||
// Gale start - EMC - reduce hopper item checks
|
||||
public Hopper hopper;
|
||||
|
||||
public class Hopper extends ConfigurationPart {
|
||||
|
||||
public int interval = 1;
|
||||
|
||||
public Minecart minecart;
|
||||
|
||||
public class Minecart extends ConfigurationPart {
|
||||
|
||||
public int interval = 1;
|
||||
|
||||
public TemporaryImmunity temporaryImmunity;
|
||||
|
||||
public class TemporaryImmunity extends ConfigurationPart {
|
||||
public int duration = 100;
|
||||
public int nearbyItemMaxAge = 1200;
|
||||
public int checkForMinecartNearItemInterval = 20;
|
||||
public boolean checkForMinecartNearItemWhileInactive = true;
|
||||
public double maxItemHorizontalDistance = 24.0;
|
||||
public double maxItemVerticalDistance = 4.0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// Gale end - EMC - reduce hopper item checks
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public LoadChunks loadChunks;
|
||||
|
||||
public class LoadChunks extends ConfigurationPart {
|
||||
public boolean toSpawnPhantoms = false; // Gale - MultiPaper - don't load chunks to spawn phantoms
|
||||
public boolean toActivateClimbingEntities = false; // Gale - don't load chunks to activate climbing entities
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public GameplayMechanics gameplayMechanics;
|
||||
|
||||
public class GameplayMechanics extends ConfigurationPart {
|
||||
|
||||
public Fixes fixes;
|
||||
|
||||
public class Fixes extends ConfigurationPart {
|
||||
|
||||
public boolean broadcastCritAnimationsAsTheEntityBeingCritted = false; // Gale - MultiPaper - broadcast crit animations as the entity being critted
|
||||
|
||||
// Gale start - Purpur - fix MC-238526
|
||||
@Setting("mc-238526")
|
||||
public boolean mc238526 = false;
|
||||
// Gale end - Purpur - fix MC-238526
|
||||
|
||||
// Gale start - Purpur - fix MC-121706
|
||||
@Setting("mc-121706")
|
||||
public boolean mc121706 = false;
|
||||
// Gale end - Purpur - fix MC-121706
|
||||
|
||||
}
|
||||
|
||||
public boolean arrowMovementResetsDespawnCounter = true; // Gale - Purpur - make arrow movement resetting despawn counter configurable
|
||||
public boolean entitiesCanRandomStrollIntoNonTickingChunks = true; // Gale - MultiPaper - prevent entities random strolling into non-ticking chunks
|
||||
public double entityWakeUpDurationRatioStandardDeviation = 0.2; // Gale - variable entity wake-up duration
|
||||
public boolean hideFlamesOnEntitiesWithFireResistance = false; // Gale - Slice - hide flames on entities with fire resistance
|
||||
public boolean tryRespawnEnderDragonAfterEndCrystalPlace = true; // Gale - Pufferfish - make ender dragon respawn attempt after placing end crystals configurable
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Gale - branding changes - version fetcher
|
||||
|
||||
package org.galemc.gale.version;
|
||||
|
||||
import com.destroystokyo.paper.PaperVersionFetcher;
|
||||
import com.destroystokyo.paper.VersionHistoryManager;
|
||||
import com.destroystokyo.paper.util.VersionFetcher;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.papermc.paper.ServerBuildInfo;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
import static net.kyori.adventure.text.format.TextColor.color;
|
||||
|
||||
/**
|
||||
* An abstract version fetcher, derived from {@link PaperVersionFetcher}.
|
||||
* This class was then made to be a superclass of both {@link PaperVersionFetcher}
|
||||
* and {@link GaleVersionFetcher}.
|
||||
* <br>
|
||||
* Changes to {@link PaperVersionFetcher} are indicated by Gale marker comments.
|
||||
*/
|
||||
public abstract class AbstractPaperVersionFetcher implements VersionFetcher {
|
||||
protected static final Logger LOGGER = LogUtils.getClassLogger();
|
||||
protected static final int DISTANCE_ERROR = -1;
|
||||
protected static final int DISTANCE_UNKNOWN = -2;
|
||||
protected static final ServerBuildInfo BUILD_INFO = ServerBuildInfo.buildInfo();
|
||||
|
||||
// Gale start - branding changes - version fetcher
|
||||
protected final String gitHubBranchName;
|
||||
protected final String downloadPage;
|
||||
protected final String organizationDisplayName;
|
||||
protected final String projectDisplayName;
|
||||
protected final String gitHubOrganizationName;
|
||||
protected final String gitHubRepoName;
|
||||
|
||||
protected AbstractPaperVersionFetcher(String githubBranchName, String downloadPage, String organizationDisplayName, String projectDisplayName, String gitHubOrganizationName, String gitHubRepoName) {
|
||||
this.gitHubBranchName = githubBranchName;
|
||||
this.downloadPage = downloadPage;
|
||||
this.organizationDisplayName = organizationDisplayName;
|
||||
this.projectDisplayName = projectDisplayName;
|
||||
this.gitHubOrganizationName = gitHubOrganizationName;
|
||||
this.gitHubRepoName = gitHubRepoName;
|
||||
}
|
||||
// Gale end - branding changes - version fetcher
|
||||
|
||||
@Override
|
||||
public long getCacheTime() {
|
||||
return 720000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Component getVersionMessage(final @NotNull String serverVersion) {
|
||||
final Component updateMessage;
|
||||
final ServerBuildInfo build = ServerBuildInfo.buildInfo();
|
||||
if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) {
|
||||
updateMessage = text("You are running a development version without access to version information", color(0xFF5300));
|
||||
} else {
|
||||
updateMessage = getUpdateStatusMessage(this.gitHubOrganizationName + "/" + this.gitHubRepoName, build); // Gale - branding changes - version fetcher
|
||||
}
|
||||
final @Nullable Component history = this.getHistory();
|
||||
|
||||
return history != null ? Component.textOfChildren(updateMessage, Component.newline(), history) : updateMessage;
|
||||
}
|
||||
|
||||
// Gale start - branding changes - version fetcher
|
||||
protected boolean canFetchDistanceFromSiteApi() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected int fetchDistanceFromSiteApi(int jenkinsBuild) {
|
||||
return -1;
|
||||
}
|
||||
// Gale end - branding changes - version fetcher
|
||||
|
||||
private Component getUpdateStatusMessage(final String repo, final ServerBuildInfo build) {
|
||||
int distance = DISTANCE_ERROR;
|
||||
|
||||
// Gale start - branding changes - version fetcher
|
||||
final Optional<String> gitBranch = build.gitBranch();
|
||||
final Optional<String> gitCommit = build.gitCommit();
|
||||
if (gitBranch.isPresent() && gitCommit.isPresent()) {
|
||||
distance = fetchDistanceFromGitHub(repo, gitBranch.get(), gitCommit.get());
|
||||
}
|
||||
// Gale end - branding changes - version fetcher
|
||||
|
||||
return switch (distance) {
|
||||
case DISTANCE_ERROR -> text("Error obtaining version information", NamedTextColor.YELLOW);
|
||||
case 0 -> text("You are running the latest version", NamedTextColor.GREEN);
|
||||
case DISTANCE_UNKNOWN -> text("Unknown version", NamedTextColor.YELLOW);
|
||||
default -> text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW)
|
||||
.append(Component.newline())
|
||||
.append(text("Download the new version at: ")
|
||||
.append(text(this.downloadPage, NamedTextColor.GOLD) // Gale - branding changes - version fetcher
|
||||
.hoverEvent(text("Click to open", NamedTextColor.WHITE))
|
||||
.clickEvent(ClickEvent.openUrl(this.downloadPage)))); // Gale - branding changes - version fetcher
|
||||
};
|
||||
}
|
||||
|
||||
// Contributed by Techcable <Techcable@outlook.com> in GH-65
|
||||
private static int fetchDistanceFromGitHub(final String repo, final String branch, final String hash) {
|
||||
try {
|
||||
final HttpURLConnection connection = (HttpURLConnection) URI.create("https://api.github.com/repos/%s/compare/%s...%s".formatted(repo, branch, hash)).toURL().openConnection();
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND)
|
||||
return DISTANCE_UNKNOWN; // Unknown commit
|
||||
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) {
|
||||
final JsonObject obj = new Gson().fromJson(reader, JsonObject.class);
|
||||
final String status = obj.get("status").getAsString();
|
||||
return switch (status) {
|
||||
case "identical" -> 0;
|
||||
case "behind" -> obj.get("behind_by").getAsInt();
|
||||
default -> DISTANCE_ERROR;
|
||||
};
|
||||
} catch (final JsonSyntaxException | NumberFormatException e) {
|
||||
LOGGER.error("Error parsing json from GitHub's API", e);
|
||||
return DISTANCE_ERROR;
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error("Error while parsing version", e);
|
||||
return DISTANCE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Component getHistory() {
|
||||
final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData();
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final @Nullable String oldVersion = data.getOldVersion();
|
||||
if (oldVersion == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Gale - semantic version
|
||||
|
||||
package org.galemc.gale.version;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A holder for the Gale semantic version.
|
||||
*/
|
||||
public final class GaleSemanticVersion {
|
||||
|
||||
private GaleSemanticVersion() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
/**
|
||||
* A semantic version in the format "<code>major.minor.patch</code>", for example "<code>1.5.1</code>".
|
||||
* The <code>major</code> version is incremented when a large and overarching set of features, with a large
|
||||
* and overarching common goal or effect, has been added compared to the first release with that major version.
|
||||
* The <code>minor</code> version is incremented for each build that has a different intended feature set
|
||||
* (for example, some features or part of them were added or removed).
|
||||
* The <code>patch</code> version is incremented for small changes that do not affect the goal of any feature,
|
||||
* such as bug fixes, performance improvements or changes in wording.
|
||||
*/
|
||||
public static final @NotNull String version = "0.6.15";
|
||||
|
||||
/**
|
||||
* The "<code>major.minor</code>" portion of the {@link #version}.
|
||||
*/
|
||||
public static final @NotNull String majorMinorVersion;
|
||||
|
||||
static {
|
||||
int firstDotIndex = version.indexOf('.');
|
||||
int secondDotIndex = version.indexOf('.', firstDotIndex + 1);
|
||||
majorMinorVersion = version.substring(0, secondDotIndex);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Gale - branding changes - version fetcher
|
||||
|
||||
package org.galemc.gale.version;
|
||||
|
||||
public class GaleVersionFetcher extends AbstractPaperVersionFetcher {
|
||||
|
||||
public GaleVersionFetcher() {
|
||||
super(
|
||||
"ver/1.21.4",
|
||||
"https://github.com/Dreeam-qwq/Gale",
|
||||
"GaleMC",
|
||||
"Gale",
|
||||
"GaleMC",
|
||||
"Gale");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Gale - virtual thread support
|
||||
|
||||
package org.galemc.gale.virtualthread;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* An implementation of {@link VirtualThreadService} that can create virtual threads directly.
|
||||
*
|
||||
* @author Martijn Muijsers
|
||||
*/
|
||||
final class DirectVirtualThreadService extends VirtualThreadService {
|
||||
|
||||
private DirectVirtualThreadService() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ThreadFactory createFactory() {
|
||||
// Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable
|
||||
//throw new UnsupportedOperationException();
|
||||
return Thread.ofVirtual().factory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Thread start(@NotNull Runnable task) {
|
||||
// Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable
|
||||
//throw new UnsupportedOperationException();
|
||||
Objects.requireNonNull(task, "The task to start a virtual thread cannot be null");
|
||||
return Thread.ofVirtual().start(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A functional {@link DirectVirtualThreadService}.
|
||||
* @throws Throwable If creating virtual threads directly is not supported by the current runtime.
|
||||
* This could be any {@link Throwable}, including an {@link Exception} or an {@link Error}.
|
||||
*/
|
||||
static @NotNull DirectVirtualThreadService create() throws Throwable {
|
||||
// Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable
|
||||
//throw new UnsupportedOperationException();
|
||||
var service = new DirectVirtualThreadService();
|
||||
// Run some tests to verify
|
||||
service.runTest();
|
||||
// If we end up here, it works
|
||||
return service;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Gale - virtual thread support
|
||||
|
||||
package org.galemc.gale.virtualthread;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* An implementation of {@link VirtualThreadService} that can create virtual threads using Java reflection.
|
||||
*
|
||||
* @author Martijn Muijsers
|
||||
*/
|
||||
final class ReflectionVirtualThreadService extends VirtualThreadService {
|
||||
|
||||
/**
|
||||
* The {@link Thread}<code>#ofVirtual()</code> method.
|
||||
*/
|
||||
private final @NotNull Method Thread_ofVirtual_method;
|
||||
|
||||
/**
|
||||
* The {@link Thread}<code>.Builder#factory()</code> method.
|
||||
*/
|
||||
private final @NotNull Method Thread_Builder_factory_method;
|
||||
|
||||
/**
|
||||
* The {@link Thread}<code>.Builder#start(Runnable)</code> method.
|
||||
*/
|
||||
private final @NotNull Method Thread_Builder_start_method;
|
||||
|
||||
private ReflectionVirtualThreadService() throws Throwable {
|
||||
this.Thread_ofVirtual_method = Objects.requireNonNull(Thread.class.getMethod("ofVirtual"));
|
||||
// The Thread.Builder class
|
||||
var Thread_Builder_class = Objects.requireNonNull(Class.forName("java.lang.Thread$Builder"));
|
||||
this.Thread_Builder_factory_method = Objects.requireNonNull(Thread_Builder_class.getMethod("factory"));
|
||||
this.Thread_Builder_start_method = Objects.requireNonNull(Thread_Builder_class.getMethod("start", Runnable.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ThreadFactory createFactory() {
|
||||
try {
|
||||
return (ThreadFactory) this.Thread_Builder_factory_method.invoke(this.Thread_ofVirtual_method.invoke(null));
|
||||
} catch (Exception e) {
|
||||
// This should not be possible because it was tested in create()
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Thread start(@NotNull Runnable task) {
|
||||
Objects.requireNonNull(task, "The task to start a virtual thread cannot be null");
|
||||
try {
|
||||
return (Thread) this.Thread_Builder_start_method.invoke(this.Thread_ofVirtual_method.invoke(null), task);
|
||||
} catch (Exception e) {
|
||||
// This should not be possible because it was tested in create()
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A functional {@link ReflectionVirtualThreadService}.
|
||||
* @throws Throwable If creating virtual threads via reflection is not supported by the current runtime.
|
||||
* This could be any {@link Throwable}, including an {@link Exception} or an {@link Error}.
|
||||
*/
|
||||
static @NotNull ReflectionVirtualThreadService create() throws Throwable {
|
||||
// This will already throw something if the reflection fails
|
||||
var service = new ReflectionVirtualThreadService();
|
||||
// Run some tests to verify
|
||||
service.runTest();
|
||||
// If we end up here, it works
|
||||
return service;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Gale - virtual thread support
|
||||
|
||||
package org.galemc.gale.virtualthread;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* An abstract service to create virtual threads.
|
||||
*
|
||||
* @author Martijn Muijsers
|
||||
*/
|
||||
public sealed abstract class VirtualThreadService permits ReflectionVirtualThreadService, DirectVirtualThreadService {
|
||||
|
||||
/**
|
||||
* @return A {@link ThreadFactory} that produces virtual threads.
|
||||
*/
|
||||
public abstract @NotNull ThreadFactory createFactory();
|
||||
|
||||
/**
|
||||
* @param task The runnable for the thread to execute.
|
||||
* @return A virtual thread that has been started with the given task.
|
||||
*/
|
||||
public abstract @NotNull Thread start(Runnable task);
|
||||
|
||||
/**
|
||||
* Runs a test on the {@link #createFactory} and {@link #start} methods,
|
||||
* which certainly throws some {@link Throwable} if something goes wrong.
|
||||
*/
|
||||
protected void runTest() throws Throwable {
|
||||
// This will definitely throw something if it doesn't work
|
||||
try {
|
||||
this.start(() -> {}).join();
|
||||
} catch (InterruptedException ignored) {} // Except InterruptedException, we don't care about that one
|
||||
try {
|
||||
var thread = this.createFactory().newThread(() -> {});
|
||||
thread.start();
|
||||
thread.join();
|
||||
} catch (InterruptedException ignored) {} // Except InterruptedException, we don't care about that one
|
||||
// If we end up here, it works
|
||||
}
|
||||
|
||||
private static boolean initialized = false;
|
||||
|
||||
/**
|
||||
* The {@link VirtualThreadService} for the current runtime,
|
||||
* or null if virtual threads are not supported, or if not {@link #initialized} yet.
|
||||
*/
|
||||
private static @Nullable VirtualThreadService implementation;
|
||||
|
||||
/**
|
||||
* @return Whether virtual threads are supported on the current runtime.
|
||||
*/
|
||||
public static boolean isSupported() {
|
||||
return get() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link VirtualThreadService} for the current runtime,
|
||||
* or null if virtual threads are not {@linkplain #isSupported() supported}.
|
||||
* <p>
|
||||
* This method is thread-safe only after the first time it has been fully run.
|
||||
*/
|
||||
public static @Nullable VirtualThreadService get() {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
try {
|
||||
implementation = DirectVirtualThreadService.create();
|
||||
} catch (Throwable ignored) {
|
||||
try {
|
||||
implementation = ReflectionVirtualThreadService.create();
|
||||
} catch (Throwable ignored2) {}
|
||||
}
|
||||
}
|
||||
return implementation;
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum major version of Java that is known to support using virtual threads
|
||||
* (although possibly behind a feature preview flag).
|
||||
*/
|
||||
public static final int minimumJavaMajorVersionWithFeaturePreview = 19;
|
||||
|
||||
/**
|
||||
* The minimum major version of Java that is known to support using virtual threads
|
||||
* even without any feature preview flags.
|
||||
*/
|
||||
public static final int minimumJavaMajorVersionWithoutFeaturePreview = 21;
|
||||
|
||||
public static int getJavaMajorVersion() {
|
||||
var version = System.getProperty("java.version");
|
||||
if (version.startsWith("1.")) {
|
||||
return version.charAt(2) - '0';
|
||||
}
|
||||
if (version.contains("-")) {
|
||||
version = version.substring(0, version.indexOf("-"));
|
||||
}
|
||||
|
||||
int dotIndex = version.indexOf(".");
|
||||
return Integer.parseInt(dotIndex == -1 ? version : version.substring(0, dotIndex));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user