mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-24 01:19:25 +00:00
686 lines
27 KiB
Diff
686 lines
27 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: wangxyper <wangxyper@163.com>
|
|
Date: Sun, 15 Jan 2023 09:56:15 +0800
|
|
Subject: [PATCH] Hearse: Base codes
|
|
|
|
Original license: MIT
|
|
Original project: https://github.com/NaturalCodeClub/Hearse
|
|
|
|
diff --git a/src/main/java/co/earthme/hearse/Hearse.java b/src/main/java/co/earthme/hearse/Hearse.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..692fef51b2f15dd1ddc28773a381b9da3b42725e
|
|
--- /dev/null
|
|
+++ b/src/main/java/co/earthme/hearse/Hearse.java
|
|
@@ -0,0 +1,27 @@
|
|
+package co.earthme.hearse;
|
|
+
|
|
+import co.earthme.hearse.commands.EntityCountCommand;
|
|
+import co.earthme.hearse.commands.WorkerCommand;
|
|
+import co.earthme.hearse.server.ServerEntityTickHook;
|
|
+import co.earthme.hearse.workers.WorkerThreadPoolManager;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+
|
|
+public class Hearse {
|
|
+ private static final WorkerThreadPoolManager workerManager = new WorkerThreadPoolManager();
|
|
+
|
|
+ public static void initAll(){
|
|
+ HearseConfig.init();
|
|
+ ServerEntityTickHook.init();
|
|
+ MinecraftServer.getServer().server.getCommandMap().register("workers","hearse",new WorkerCommand());
|
|
+ MinecraftServer.getServer().server.getCommandMap().register("entitycount","hearse",new EntityCountCommand());
|
|
+ }
|
|
+
|
|
+ public static void onServerStop(){
|
|
+ HearseConfig.save();
|
|
+ workerManager.shutdownAllNow();
|
|
+ }
|
|
+
|
|
+ public static WorkerThreadPoolManager getWorkerManager() {
|
|
+ return workerManager;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/co/earthme/hearse/HearseConfig.java b/src/main/java/co/earthme/hearse/HearseConfig.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..73b5e76660b5162a7a0b327ddc7dcc3295b86699
|
|
--- /dev/null
|
|
+++ b/src/main/java/co/earthme/hearse/HearseConfig.java
|
|
@@ -0,0 +1,49 @@
|
|
+package co.earthme.hearse;
|
|
+
|
|
+import org.bukkit.configuration.InvalidConfigurationException;
|
|
+import org.bukkit.configuration.file.YamlConfiguration;
|
|
+import java.io.File;
|
|
+import java.io.IOException;
|
|
+
|
|
+public class HearseConfig {
|
|
+ private static final YamlConfiguration configEntry = new YamlConfiguration();
|
|
+ private static final File CONFIG_FILE = new File("hearse.yml");
|
|
+
|
|
+ public static void init(){
|
|
+ try {
|
|
+ configEntry.load(CONFIG_FILE);
|
|
+ }catch (IOException ignored){
|
|
+ } catch (InvalidConfigurationException e) {
|
|
+ e.printStackTrace();
|
|
+ }
|
|
+ configEntry.options().copyDefaults(true);
|
|
+ }
|
|
+
|
|
+ public static void save(){
|
|
+ try {
|
|
+ configEntry.save(CONFIG_FILE);
|
|
+ } catch (IOException e) {
|
|
+ e.printStackTrace();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static int getInt(String key,int def){
|
|
+ configEntry.addDefault(key,def);
|
|
+ return configEntry.getInt(key);
|
|
+ }
|
|
+
|
|
+ public static long getLong(String key,int def){
|
|
+ configEntry.addDefault(key,def);
|
|
+ return configEntry.getLong(key);
|
|
+ }
|
|
+
|
|
+ public static String getString(String key,String def){
|
|
+ configEntry.addDefault(key,def);
|
|
+ return configEntry.getString(key);
|
|
+ }
|
|
+
|
|
+ public static boolean getBoolean(String key,boolean def){
|
|
+ configEntry.addDefault(key,def);
|
|
+ return configEntry.getBoolean(key);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/co/earthme/hearse/commands/EntityCountCommand.java b/src/main/java/co/earthme/hearse/commands/EntityCountCommand.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..de759c808040058062078130b527e78215216ebb
|
|
--- /dev/null
|
|
+++ b/src/main/java/co/earthme/hearse/commands/EntityCountCommand.java
|
|
@@ -0,0 +1,36 @@
|
|
+package co.earthme.hearse.commands;
|
|
+
|
|
+import com.google.common.collect.Maps;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.world.entity.Entity;
|
|
+import org.bukkit.ChatColor;
|
|
+import org.bukkit.command.Command;
|
|
+import org.bukkit.command.CommandSender;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+import java.util.Map;
|
|
+
|
|
+public class EntityCountCommand extends Command {
|
|
+ public EntityCountCommand() {
|
|
+ super("entitycount");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
|
|
+ final Map<String,Integer> counts = Maps.newHashMap();
|
|
+ for (ServerLevel level : MinecraftServer.getServer().getAllLevels()){
|
|
+ for (Entity entity : level.entityTickList.entities){
|
|
+ final String name = entity.getType().getName();
|
|
+ if (!counts.containsKey(name)){
|
|
+ counts.put(name,0);
|
|
+ }
|
|
+ counts.replace(name,counts.get(name)+1);
|
|
+ }
|
|
+ }
|
|
+ sender.sendMessage("Exists entity Counts:");
|
|
+ for (Map.Entry<String,Integer> entry : counts.entrySet()){
|
|
+ sender.sendMessage(ChatColor.BLUE+String.format("%s:%s",entry.getKey(),entry.getValue()));
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/co/earthme/hearse/commands/WorkerCommand.java b/src/main/java/co/earthme/hearse/commands/WorkerCommand.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..1a4a6869a7278beadd97af006f4b5fae578b83ed
|
|
--- /dev/null
|
|
+++ b/src/main/java/co/earthme/hearse/commands/WorkerCommand.java
|
|
@@ -0,0 +1,72 @@
|
|
+package co.earthme.hearse.commands;
|
|
+
|
|
+import co.earthme.hearse.Hearse;
|
|
+import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor;
|
|
+import org.bukkit.ChatColor;
|
|
+import org.bukkit.command.Command;
|
|
+import org.bukkit.command.CommandSender;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+import java.util.ArrayList;
|
|
+import java.util.List;
|
|
+import java.util.Map;
|
|
+
|
|
+public class WorkerCommand extends Command {
|
|
+ public WorkerCommand() {
|
|
+ super("workers");
|
|
+ this.setPermission("hearse.commands.workers");
|
|
+ this.setDescription("You can see or edit the server workers by using this command");
|
|
+ this.setUsage("/workers <status,setThreadCount,forceStop> <workername>");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException {
|
|
+ final List<String> ret = new ArrayList<>();
|
|
+ if (args.length == 1){
|
|
+ ret.add("status");
|
|
+ ret.add("setThreadCount");
|
|
+ ret.add("forceStop");
|
|
+ }
|
|
+ if (args.length == 2){
|
|
+ for (Map.Entry<String, WorkerThreadPoolExecutor> entry : Hearse.getWorkerManager().getManagedWorkers().entrySet()){
|
|
+ ret.add(entry.getKey());
|
|
+ }
|
|
+ }
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
|
|
+ if (args.length >= 2){
|
|
+ final String action = args[0];
|
|
+ final String workerName = args[1];
|
|
+ final WorkerThreadPoolExecutor searchedWorker = Hearse.getWorkerManager().getTargetWorker(workerName);
|
|
+ if (searchedWorker == null){
|
|
+ sender.sendMessage(ChatColor.RED+"Target worker not found!");
|
|
+ return true;
|
|
+ }
|
|
+ switch (action){
|
|
+ case "status":
|
|
+ sender.sendMessage(ChatColor.GREEN+"Worker: "+workerName+" Status:"+ searchedWorker);
|
|
+ break;
|
|
+ case "setThreadCount":
|
|
+ if (args.length == 3){
|
|
+ try {
|
|
+ searchedWorker.setCorePoolSize(Integer.parseInt(args[2]));
|
|
+ sender.sendMessage(ChatColor.GREEN+"Finished!");
|
|
+ }catch (NumberFormatException e){
|
|
+ sender.sendMessage(ChatColor.RED+"Please supply an integer!");
|
|
+ }
|
|
+ }else{
|
|
+ sender.sendMessage(ChatColor.RED+"Please supply an integer!");
|
|
+ }
|
|
+ break;
|
|
+ case "forceStop":
|
|
+ searchedWorker.shutdownNow();
|
|
+ sender.sendMessage(ChatColor.YELLOW+"Worker "+workerName+" has been stopped!");
|
|
+ break;
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/co/earthme/hearse/concurrent/WorkerThread.java b/src/main/java/co/earthme/hearse/concurrent/WorkerThread.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..421d4926ac674b5eb12d9613ceb6d20185ea557d
|
|
--- /dev/null
|
|
+++ b/src/main/java/co/earthme/hearse/concurrent/WorkerThread.java
|
|
@@ -0,0 +1,18 @@
|
|
+package co.earthme.hearse.concurrent;
|
|
+
|
|
+import io.papermc.paper.util.TickThread;
|
|
+
|
|
+public class WorkerThread extends TickThread {
|
|
+
|
|
+ public WorkerThread(String name) {
|
|
+ super(name);
|
|
+ }
|
|
+
|
|
+ public WorkerThread(Runnable run, String name) {
|
|
+ super(run, name);
|
|
+ }
|
|
+
|
|
+ public static boolean isWorker(){
|
|
+ return Thread.currentThread() instanceof WorkerThread;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/co/earthme/hearse/concurrent/WorkerThreadFactory.java b/src/main/java/co/earthme/hearse/concurrent/WorkerThreadFactory.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..e65b1eba68003a9f7ce5080d07a521817831ff48
|
|
--- /dev/null
|
|
+++ b/src/main/java/co/earthme/hearse/concurrent/WorkerThreadFactory.java
|
|
@@ -0,0 +1,5 @@
|
|
+package co.earthme.hearse.concurrent;
|
|
+
|
|
+public interface WorkerThreadFactory {
|
|
+ WorkerThread getNewThread(Runnable task);
|
|
+}
|
|
diff --git a/src/main/java/co/earthme/hearse/concurrent/WorkerThreadPoolExecutor.java b/src/main/java/co/earthme/hearse/concurrent/WorkerThreadPoolExecutor.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..7e010bf23c9fc26284212a4388172f5d7d5a4b99
|
|
--- /dev/null
|
|
+++ b/src/main/java/co/earthme/hearse/concurrent/WorkerThreadPoolExecutor.java
|
|
@@ -0,0 +1,76 @@
|
|
+package co.earthme.hearse.concurrent;
|
|
+
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+
|
|
+import java.util.Queue;
|
|
+import java.util.concurrent.*;
|
|
+import java.util.concurrent.locks.LockSupport;
|
|
+
|
|
+public class WorkerThreadPoolExecutor extends ThreadPoolExecutor {
|
|
+ private final Queue<TaskEntry> taskEntries = new ConcurrentLinkedQueue<>();
|
|
+
|
|
+ public WorkerThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, @NotNull BlockingQueue<Runnable> workQueue, @NotNull WorkerThreadFactory workerThreadFactory) {
|
|
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, workerThreadFactory::getNewThread);
|
|
+ }
|
|
+
|
|
+ public WorkerThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, @NotNull BlockingQueue<Runnable> workQueue, @NotNull WorkerThreadFactory workerThreadFactory, @NotNull RejectedExecutionHandler handler) {
|
|
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, workerThreadFactory::getNewThread, handler);
|
|
+ }
|
|
+
|
|
+ public int getCurrentNotProcessingTasks(){
|
|
+ return this.getQueue().size();
|
|
+ }
|
|
+
|
|
+ public void clearAllTasks(){
|
|
+ this.getQueue().clear();
|
|
+ }
|
|
+
|
|
+ public void executeWithSubTask(Runnable mainTask,Runnable subTask){
|
|
+ final TaskEntry wrapped = new TaskEntry(subTask,mainTask);
|
|
+ this.taskEntries.offer(wrapped);
|
|
+ this.execute(wrapped);
|
|
+ }
|
|
+
|
|
+ public void runAllSubTasks(){
|
|
+ TaskEntry task;
|
|
+ while ((task = this.taskEntries.poll())!=null){
|
|
+ while (!task.allRunned()){
|
|
+ LockSupport.parkNanos(this,10000000);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static class TaskEntry implements Runnable{
|
|
+ private final Runnable mainTask;
|
|
+ private final Runnable subTask;
|
|
+ private volatile boolean mainTaskFinished = false;
|
|
+
|
|
+ public TaskEntry(Runnable subTask,Runnable mainTask){
|
|
+ this.subTask = subTask;
|
|
+ this.mainTask = mainTask;
|
|
+ }
|
|
+
|
|
+ public boolean allRunned(){
|
|
+ if (!this.mainTaskFinished){
|
|
+ return false;
|
|
+ }
|
|
+ try {
|
|
+ this.subTask.run();
|
|
+ }catch (Exception e){
|
|
+ e.printStackTrace();
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void run() {
|
|
+ try {
|
|
+ this.mainTask.run();
|
|
+ }catch(Exception e){
|
|
+ e.printStackTrace();
|
|
+ }finally {
|
|
+ this.mainTaskFinished = true;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/co/earthme/hearse/concurrent/threadfactory/DefaultWorkerFactory.java b/src/main/java/co/earthme/hearse/concurrent/threadfactory/DefaultWorkerFactory.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..03a29509821a17faac2dc8ab810a2693b03bfbc6
|
|
--- /dev/null
|
|
+++ b/src/main/java/co/earthme/hearse/concurrent/threadfactory/DefaultWorkerFactory.java
|
|
@@ -0,0 +1,42 @@
|
|
+package co.earthme.hearse.concurrent.threadfactory;
|
|
+
|
|
+import co.earthme.hearse.concurrent.WorkerThread;
|
|
+import co.earthme.hearse.concurrent.WorkerThreadFactory;
|
|
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
+import it.unimi.dsi.fastutil.objects.ObjectLists;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+
|
|
+import java.util.List;
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
+
|
|
+public class DefaultWorkerFactory implements WorkerThreadFactory {
|
|
+ private static final AtomicInteger poolId = new AtomicInteger();
|
|
+ private final AtomicInteger threadId = new AtomicInteger();
|
|
+ private final String bound;
|
|
+ private final List<Thread> createdThreads = ObjectLists.synchronize(new ObjectArrayList<>());
|
|
+
|
|
+ public DefaultWorkerFactory(String bound){
|
|
+ poolId.getAndIncrement();
|
|
+ this.bound = bound;
|
|
+ }
|
|
+
|
|
+ public List<Thread> getCreatedThreads() {
|
|
+ return this.createdThreads;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public WorkerThread getNewThread(Runnable task) {
|
|
+ final WorkerThread workerThread = new WorkerThread(()->{
|
|
+ try {
|
|
+ task.run();
|
|
+ }finally {
|
|
+ this.createdThreads.remove(Thread.currentThread());
|
|
+ }
|
|
+ },"pool-"+poolId.get()+"-worker-"+threadId.getAndIncrement()+"-bound-"+this.bound);
|
|
+ this.createdThreads.add(workerThread);
|
|
+ workerThread.setDaemon(true);
|
|
+ workerThread.setPriority(Thread.NORM_PRIORITY - 2);
|
|
+ workerThread.setContextClassLoader(MinecraftServer.class.getClassLoader());
|
|
+ return workerThread;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java b/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..c0e7a9cf79ddf00827daba0aa9c7a32fa76b0c7c
|
|
--- /dev/null
|
|
+++ b/src/main/java/co/earthme/hearse/server/ServerEntityTickHook.java
|
|
@@ -0,0 +1,102 @@
|
|
+package co.earthme.hearse.server;
|
|
+
|
|
+import co.earthme.hearse.Hearse;
|
|
+import co.earthme.hearse.HearseConfig;
|
|
+import co.earthme.hearse.concurrent.WorkerThreadFactory;
|
|
+import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor;
|
|
+import co.earthme.hearse.concurrent.threadfactory.DefaultWorkerFactory;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.world.entity.Entity;
|
|
+import net.minecraft.world.entity.player.Player;
|
|
+import org.apache.logging.log4j.LogManager;
|
|
+import org.apache.logging.log4j.Logger;
|
|
+import java.util.concurrent.LinkedBlockingQueue;
|
|
+import java.util.concurrent.RejectedExecutionException;
|
|
+import java.util.concurrent.TimeUnit;
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
+
|
|
+public class ServerEntityTickHook {
|
|
+ private static final Logger logger = LogManager.getLogger();
|
|
+ private static volatile boolean firstTick = false;
|
|
+ private static final WorkerThreadFactory defFactory = new DefaultWorkerFactory("entity");
|
|
+ private static final AtomicInteger threadId = new AtomicInteger();
|
|
+ private static WorkerThreadPoolExecutor worker;
|
|
+ private static boolean asyncEntityEnabled;
|
|
+
|
|
+ public static void executeAsyncTask(Runnable task){
|
|
+ if (!asyncEntityEnabled){
|
|
+ throw new RejectedExecutionException();
|
|
+ }
|
|
+ worker.execute(task);
|
|
+ }
|
|
+
|
|
+ public static void init(){
|
|
+ boolean asyncEntityEnabled1 = HearseConfig.getBoolean("optimizations.enable-async-entity",true);
|
|
+ final int workerCount = HearseConfig.getInt("workers.async-entity-worker-count",Runtime.getRuntime().availableProcessors());
|
|
+ if (asyncEntityEnabled1){
|
|
+ worker = new WorkerThreadPoolExecutor(
|
|
+ workerCount,
|
|
+ workerCount,
|
|
+ 0L,
|
|
+ TimeUnit.MILLISECONDS,
|
|
+ new LinkedBlockingQueue<>(),
|
|
+ defFactory
|
|
+ );
|
|
+ Hearse.getWorkerManager().addWorker("entity",worker);
|
|
+ }
|
|
+ asyncEntityEnabled = asyncEntityEnabled1;
|
|
+ }
|
|
+
|
|
+ public static void executeAsyncTaskWithMainThreadCallback(Runnable task,Runnable callBack){
|
|
+ if (!asyncEntityEnabled){
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+ worker.executeWithSubTask(task,callBack);
|
|
+ }
|
|
+
|
|
+ public static void callTickStart(){
|
|
+ if (!firstTick){
|
|
+ firstTick = true;
|
|
+ return;
|
|
+ }
|
|
+ if (!asyncEntityEnabled){
|
|
+ return;
|
|
+ }
|
|
+ worker.runAllSubTasks();
|
|
+ }
|
|
+
|
|
+ public static void callAsyncEntityTick(Entity entity, ServerLevel level){
|
|
+ MinecraftServer.getServer().executeMidTickTasks();
|
|
+ Runnable task = ()->{
|
|
+ entity.activatedPriorityReset = false;
|
|
+ if (!entity.isRemoved()) {
|
|
+ entity.checkDespawn();
|
|
+ Entity entity1 = entity.getVehicle();
|
|
+ if (entity1 != null) {
|
|
+ if (!entity1.isRemoved() && entity1.hasPassenger(entity)) {
|
|
+ return;
|
|
+ }
|
|
+ entity.stopRiding();
|
|
+ }
|
|
+ try {
|
|
+ level.tickNonPassenger(entity);
|
|
+ } catch (Throwable throwable) {
|
|
+ if (throwable instanceof ThreadDeath) throw throwable;
|
|
+ level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(throwable.getMessage(), throwable)));
|
|
+ throwable.printStackTrace();
|
|
+ }
|
|
+ }
|
|
+ };
|
|
+ if (!asyncEntityEnabled){
|
|
+ task.run();
|
|
+ return;
|
|
+ }
|
|
+ try {
|
|
+ worker.execute(task);
|
|
+ }catch (RejectedExecutionException e){
|
|
+ logger.warn("Worker rejected our task.Falling back to sync entity updating");
|
|
+ asyncEntityEnabled = false;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java b/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..8085eb700d8e5c20ebb5bfeceb78198c6e973019
|
|
--- /dev/null
|
|
+++ b/src/main/java/co/earthme/hearse/server/ServerLevelTickHook.java
|
|
@@ -0,0 +1,78 @@
|
|
+package co.earthme.hearse.server;
|
|
+
|
|
+import co.earthme.hearse.Hearse;
|
|
+import co.earthme.hearse.HearseConfig;
|
|
+import co.earthme.hearse.concurrent.WorkerThread;
|
|
+import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor;
|
|
+import co.earthme.hearse.concurrent.threadfactory.DefaultWorkerFactory;
|
|
+import net.minecraft.CrashReport;
|
|
+import net.minecraft.ReportedException;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import org.apache.logging.log4j.LogManager;
|
|
+import org.apache.logging.log4j.Logger;
|
|
+
|
|
+import java.util.concurrent.LinkedBlockingQueue;
|
|
+import java.util.concurrent.TimeUnit;
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
+import java.util.concurrent.locks.LockSupport;
|
|
+import java.util.function.BooleanSupplier;
|
|
+
|
|
+public class ServerLevelTickHook {
|
|
+ private static final DefaultWorkerFactory workerFactory = new DefaultWorkerFactory("world");
|
|
+ private static WorkerThreadPoolExecutor worker;
|
|
+ private static boolean enabledParaWorld;
|
|
+ private static volatile boolean inited = false;
|
|
+ private static final AtomicInteger activeTaskCount = new AtomicInteger();
|
|
+ private static final Logger logger = LogManager.getLogger();
|
|
+
|
|
+ public static void initWorker(){
|
|
+ enabledParaWorld = HearseConfig.getBoolean("optimizations.enableparallelworldtick",true);
|
|
+ if (enabledParaWorld){
|
|
+ worker = new WorkerThreadPoolExecutor(
|
|
+ MinecraftServer.getServer().levels.size(),
|
|
+ MinecraftServer.getServer().levels.size(),
|
|
+ Long.MAX_VALUE,
|
|
+ TimeUnit.MILLISECONDS,
|
|
+ new LinkedBlockingQueue<>(),
|
|
+ workerFactory
|
|
+ );
|
|
+ worker.allowCoreThreadTimeOut(true);
|
|
+ worker.prestartAllCoreThreads();
|
|
+ Hearse.getWorkerManager().addWorker("world",worker);
|
|
+ for (Thread worker : workerFactory.getCreatedThreads()){
|
|
+ logger.warn("World worker name:{}.This can help you to slove the lag problems when you using parallel world ticking",worker.getName());
|
|
+ }
|
|
+ }
|
|
+ inited = true;
|
|
+ }
|
|
+
|
|
+ public static boolean isInited(){
|
|
+ return inited;
|
|
+ }
|
|
+
|
|
+ public static void callWorldTick(ServerLevel worldserver, BooleanSupplier shouldKeepTicking){
|
|
+ activeTaskCount.getAndIncrement();
|
|
+ worker.execute(()->{
|
|
+ try {
|
|
+ try {
|
|
+ worldserver.tick(shouldKeepTicking);
|
|
+ for (final io.papermc.paper.chunk.SingleThreadChunkRegionManager regionManager : worldserver.getChunkSource().chunkMap.regionManagers) {
|
|
+ regionManager.recalculateRegions();
|
|
+ }
|
|
+ } catch (Throwable throwable) {
|
|
+ throwable.printStackTrace();
|
|
+ }
|
|
+ worldserver.explosionDensityCache.clear();
|
|
+ }finally {
|
|
+ activeTaskCount.getAndDecrement();
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ public static void awaitWorldTicKTasks(){
|
|
+ while (activeTaskCount.get() > 0){
|
|
+ LockSupport.parkNanos("Await world ticking",1000000);
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/co/earthme/hearse/workers/WorkerThreadPoolManager.java b/src/main/java/co/earthme/hearse/workers/WorkerThreadPoolManager.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..527dba288e1988773fd5a89f076f92084034f421
|
|
--- /dev/null
|
|
+++ b/src/main/java/co/earthme/hearse/workers/WorkerThreadPoolManager.java
|
|
@@ -0,0 +1,68 @@
|
|
+package co.earthme.hearse.workers;
|
|
+
|
|
+import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor;
|
|
+import com.google.common.collect.Maps;
|
|
+import java.util.List;
|
|
+import java.util.Map;
|
|
+import java.util.concurrent.TimeUnit;
|
|
+
|
|
+public class WorkerThreadPoolManager {
|
|
+ private final Map<String,WorkerThreadPoolExecutor> managedWorkers = Maps.newConcurrentMap();
|
|
+
|
|
+ public void addWorker(String bound,WorkerThreadPoolExecutor worker){
|
|
+ this.managedWorkers.put(bound,worker);
|
|
+ }
|
|
+
|
|
+ public void shutdownAll() throws InterruptedException {
|
|
+ for (WorkerThreadPoolExecutor worker : this.managedWorkers.values()){
|
|
+ if (!worker.isShutdown()){
|
|
+ worker.getQueue().clear(); //Clear the tasks.We don't need wait them
|
|
+ worker.shutdown();
|
|
+ while (worker.awaitTermination(100, TimeUnit.MILLISECONDS)) {}
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Deprecated
|
|
+ public Map<String, WorkerThreadPoolExecutor> getManagedWorkers() {
|
|
+ return Maps.newHashMap(this.managedWorkers);
|
|
+ }
|
|
+
|
|
+ @Deprecated
|
|
+ public WorkerThreadPoolExecutor getTargetWorker(String bound){
|
|
+ return this.managedWorkers.get(bound);
|
|
+ }
|
|
+
|
|
+ public Map<String,List<Runnable>> shutdownAllNow(){
|
|
+ final Map<String,List<Runnable>> ret = Maps.newHashMap();
|
|
+ for (Map.Entry<String,WorkerThreadPoolExecutor> entry : this.managedWorkers.entrySet()){
|
|
+ final String workerName = entry.getKey();
|
|
+ final WorkerThreadPoolExecutor worker = entry.getValue();
|
|
+ if (!worker.isShutdown()){
|
|
+ try {
|
|
+ final List<Runnable> taskNotRunned = worker.shutdownNow();
|
|
+ ret.put(workerName,taskNotRunned);
|
|
+ }catch (Exception e){
|
|
+ e.printStackTrace();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ public void shutdownAll(long singleWorkerAwaitTimeOutCount) throws InterruptedException {
|
|
+ long counter = singleWorkerAwaitTimeOutCount;
|
|
+ for (WorkerThreadPoolExecutor worker : this.managedWorkers.values()){
|
|
+ if (!worker.isShutdown()){
|
|
+ worker.shutdown();
|
|
+ while (worker.awaitTermination(1, TimeUnit.MILLISECONDS)) {
|
|
+ if (counter == 0){
|
|
+ break;
|
|
+ }
|
|
+ counter--;
|
|
+ }
|
|
+ counter = singleWorkerAwaitTimeOutCount;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
|
|
index f7e8b6e1872a397c96afc938754726b0d4e493b4..2448673ee847fe3bc05f1269737aae5b43ae8291 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/EntityType.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
|
|
@@ -308,6 +308,16 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
|
|
return (EntityType) Registry.register(BuiltInRegistries.ENTITY_TYPE, id, (EntityType<T>) type.build(id)); // CraftBukkit - decompile error
|
|
}
|
|
|
|
+ // Purpur start
|
|
+ public static EntityType<?> getFromBukkitType(org.bukkit.entity.EntityType bukkitType) {
|
|
+ return getFromKey(new ResourceLocation(bukkitType.getKey().toString()));
|
|
+ }
|
|
+
|
|
+ public static EntityType<?> getFromKey(ResourceLocation location) {
|
|
+ return BuiltInRegistries.ENTITY_TYPE.get(location);
|
|
+ }
|
|
+ // Purpur end
|
|
+
|
|
public static ResourceLocation getKey(EntityType<?> type) {
|
|
return BuiltInRegistries.ENTITY_TYPE.getKey(type);
|
|
}
|
|
@@ -522,6 +532,16 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
|
|
return this.category;
|
|
}
|
|
|
|
+ // Purpur start
|
|
+ public String getName() {
|
|
+ return BuiltInRegistries.ENTITY_TYPE.getKey(this).getPath();
|
|
+ }
|
|
+
|
|
+ public String getTranslatedName() {
|
|
+ return getDescription().getString();
|
|
+ }
|
|
+ // Purpur end
|
|
+
|
|
public String getDescriptionId() {
|
|
if (this.descriptionId == null) {
|
|
this.descriptionId = Util.makeDescriptionId("entity", BuiltInRegistries.ENTITY_TYPE.getKey(this));
|