Fully parallel world ticking
This commit is contained in:
@@ -33,7 +33,7 @@ import java.util.logging.Level;
|
||||
* Akarin Changes Note
|
||||
* 1) Add volatile to fields (safety issue)
|
||||
*/
|
||||
class TimingHandler implements Timing {
|
||||
public class TimingHandler implements Timing { // Akarin
|
||||
|
||||
private static int idPool = 1;
|
||||
final int id = idPool++;
|
||||
@@ -124,6 +124,14 @@ class TimingHandler implements Timing {
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Akarin start
|
||||
public void stopTiming(long start) {
|
||||
if (enabled && --timingDepth == 0 && start != 0) {
|
||||
addDiff(System.nanoTime() - start);
|
||||
}
|
||||
}
|
||||
// Akarin end
|
||||
|
||||
@Override
|
||||
public void abort() {
|
||||
|
||||
@@ -4,8 +4,6 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -17,6 +15,7 @@ import co.aikar.timings.Timing;
|
||||
import co.aikar.timings.Timings;
|
||||
import io.akarin.server.core.AkarinGlobalConfig;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.WorldServer;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public abstract class Akari {
|
||||
@@ -54,10 +53,8 @@ public abstract class Akari {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A common tick pool
|
||||
*/
|
||||
public static ExecutorCompletionService<?> STAGE_TICK;
|
||||
public static ExecutorCompletionService<WorldServer> STAGE_ENTITY_TICK;
|
||||
public static ExecutorCompletionService<WorldServer> STAGE_WORLD_TICK;
|
||||
|
||||
public static boolean isPrimaryThread() {
|
||||
return isPrimaryThread(true);
|
||||
@@ -97,8 +94,6 @@ public abstract class Akari {
|
||||
*/
|
||||
public final static Timing worldTiming = getTiming("Akarin - Full World Tick");
|
||||
|
||||
public final static Timing entityCallbackTiming = getTiming("Akarin - Entity Parallell Await");
|
||||
|
||||
public final static Timing callbackTiming = getTiming("Akarin - Callback Queue");
|
||||
|
||||
private static Timing getTiming(String name) {
|
||||
|
||||
@@ -165,11 +165,6 @@ public class AkarinGlobalConfig {
|
||||
silentAsyncTimings = getBoolean("core.always-silent-async-timing", false);
|
||||
}
|
||||
|
||||
public static boolean legacyWorldTimings;
|
||||
private static void legacyWorldTimings() {
|
||||
legacyWorldTimings = getBoolean("alternative.legacy-world-timings-required", false);
|
||||
}
|
||||
|
||||
public static long timeUpdateInterval;
|
||||
private static void timeUpdateInterval() {
|
||||
timeUpdateInterval = getSeconds(getString("core.tick-rate.world-time-update-interval", "1s")) * 10;
|
||||
@@ -201,52 +196,25 @@ public class AkarinGlobalConfig {
|
||||
}
|
||||
|
||||
public static String messageKick;
|
||||
private static void messageKick() {
|
||||
messageKick = getString("messages.disconnect.kick-player", "Kicked by an operator.");
|
||||
}
|
||||
|
||||
public static String messageBan;
|
||||
private static void messageBan() {
|
||||
messageBan = getString("messages.disconnect.ban-player-name", "You are banned from this server! %s %s");
|
||||
}
|
||||
|
||||
public static String messageBanReason;
|
||||
private static void messageBanReason() {
|
||||
messageBanReason = getString("messages.disconnect.ban-reason", "\nReason: ");
|
||||
}
|
||||
|
||||
public static String messageBanExpires;
|
||||
private static void messageBanExpires() {
|
||||
messageBanExpires = getString("messages.disconnect.ban-expires", "\nYour ban will be removed on ");
|
||||
}
|
||||
|
||||
public static String messageBanIp;
|
||||
private static void messageBanIp() {
|
||||
messageBanIp = getString("messages.disconnect.ban-player-ip", "Your IP address is banned from this server! %s %s");
|
||||
}
|
||||
|
||||
public static String messageDupLogin;
|
||||
private static void messageDupLogin() {
|
||||
messageDupLogin = getString("messages.disconnect.kick-player-duplicate-login", "You logged in from another location");
|
||||
}
|
||||
|
||||
public static String messageJoin;
|
||||
private static void messageJoin() {
|
||||
messageJoin = getString("messages.connect.player-join-server", "§e%s joined the game");
|
||||
}
|
||||
|
||||
public static String messageJoinRenamed;
|
||||
private static void messageJoinRenamed() {
|
||||
messageJoinRenamed = getString("messages.connect.renamed-player-join-server", "§e%s (formerly known as %s) joined the game");
|
||||
}
|
||||
|
||||
public static String messageKickKeepAlive;
|
||||
private static void messagekickKeepAlive() {
|
||||
messageKickKeepAlive = getString("messages.disconnect.kick-player-timeout-keep-alive", "Timed out");
|
||||
}
|
||||
|
||||
public static String messagePlayerQuit;
|
||||
private static void messagePlayerQuit() {
|
||||
private static void messagekickKeepAlive() {
|
||||
messageKick = getString("messages.disconnect.kick-player", "Kicked by an operator.");
|
||||
messageBan = getString("messages.disconnect.ban-player-name", "You are banned from this server! %s %s");
|
||||
messageBanReason = getString("messages.disconnect.ban-reason", "\nReason: ");
|
||||
messageBanExpires = getString("messages.disconnect.ban-expires", "\nYour ban will be removed on ");
|
||||
messageBanIp = getString("messages.disconnect.ban-player-ip", "Your IP address is banned from this server! %s %s");
|
||||
messageDupLogin = getString("messages.disconnect.kick-player-duplicate-login", "You logged in from another location");
|
||||
messageJoin = getString("messages.connect.player-join-server", "§e%s joined the game");
|
||||
messageJoinRenamed = getString("messages.connect.renamed-player-join-server", "§e%s (formerly known as %s) joined the game");
|
||||
messageKickKeepAlive = getString("messages.disconnect.kick-player-timeout-keep-alive", "Timed out");
|
||||
messagePlayerQuit = getString("messages.disconnect.player-quit-server", "§e%s left the game");
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,14 @@ package io.akarin.server.mixin.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
import org.apache.commons.lang.WordUtils;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor;
|
||||
import org.bukkit.event.inventory.InventoryMoveItemEvent;
|
||||
@@ -20,6 +24,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import co.aikar.timings.MinecraftTimings;
|
||||
import co.aikar.timings.TimingHandler;
|
||||
import io.akarin.api.internal.Akari;
|
||||
import io.akarin.api.internal.Akari.AssignableFactory;
|
||||
import io.akarin.api.internal.mixin.IMixinLockProvider;
|
||||
@@ -41,7 +46,8 @@ import net.minecraft.server.WorldServer;
|
||||
@Mixin(value = MinecraftServer.class, remap = false)
|
||||
public abstract class MixinMinecraftServer {
|
||||
@Shadow @Final public Thread primaryThread;
|
||||
private int cachedWorlds;
|
||||
private boolean tickedPrimaryEntities;
|
||||
private int cachedWorldSize;
|
||||
|
||||
@Overwrite
|
||||
public String getServerModName() {
|
||||
@@ -54,7 +60,8 @@ public abstract class MixinMinecraftServer {
|
||||
shift = At.Shift.BEFORE
|
||||
))
|
||||
private void prerun(CallbackInfo info) {
|
||||
Akari.STAGE_TICK = new ExecutorCompletionService<>(Executors.newFixedThreadPool((cachedWorlds = worlds.size()), new AssignableFactory()));
|
||||
Akari.STAGE_ENTITY_TICK = new ExecutorCompletionService<>(Executors.newFixedThreadPool((cachedWorldSize = worlds.size()), new AssignableFactory()));
|
||||
Akari.STAGE_WORLD_TICK = new ExecutorCompletionService<>(Executors.newFixedThreadPool(cachedWorldSize - 1, new AssignableFactory()));
|
||||
|
||||
primaryThread.setPriority(AkarinGlobalConfig.primaryThreadPriority < Thread.NORM_PRIORITY ? Thread.NORM_PRIORITY :
|
||||
(AkarinGlobalConfig.primaryThreadPriority > Thread.MAX_PRIORITY ? 10 : AkarinGlobalConfig.primaryThreadPriority));
|
||||
@@ -180,10 +187,12 @@ public abstract class MixinMinecraftServer {
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public void D() throws InterruptedException {
|
||||
if (worlds.size() != cachedWorlds) Akari.STAGE_TICK = new ExecutorCompletionService<>(Executors.newFixedThreadPool(cachedWorlds, new AssignableFactory())); // Resize
|
||||
|
||||
public void D() throws InterruptedException, ExecutionException, CancellationException {
|
||||
Runnable runnable;
|
||||
Akari.callbackTiming.startTiming();
|
||||
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
|
||||
Akari.callbackTiming.stopTiming();
|
||||
|
||||
MinecraftTimings.bukkitSchedulerTimer.startTiming();
|
||||
this.server.getScheduler().mainThreadHeartbeat(this.ticks);
|
||||
MinecraftTimings.bukkitSchedulerTimer.stopTiming();
|
||||
@@ -205,44 +214,69 @@ public abstract class MixinMinecraftServer {
|
||||
MinecraftTimings.chunkIOTickTimer.stopTiming();
|
||||
|
||||
Akari.worldTiming.startTiming();
|
||||
if (AkarinGlobalConfig.legacyWorldTimings) {
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
world.timings.tickEntities.startTiming();
|
||||
world.timings.doTick.startTiming();
|
||||
}
|
||||
// Resize
|
||||
if (cachedWorldSize != worlds.size()) {
|
||||
Akari.STAGE_ENTITY_TICK = new ExecutorCompletionService<>(Executors.newFixedThreadPool(cachedWorldSize = worlds.size(), new AssignableFactory()));
|
||||
Akari.STAGE_WORLD_TICK = new ExecutorCompletionService<>(Executors.newFixedThreadPool(cachedWorldSize - 1, new AssignableFactory()));
|
||||
}
|
||||
|
||||
tickedPrimaryEntities = false;
|
||||
// Never tick one world concurrently!
|
||||
for (int i = 0; i < worlds.size(); i++) {
|
||||
int interlace = i + 1;
|
||||
WorldServer entitiesWorld = worlds.get(interlace < worlds.size() ? interlace : 0);
|
||||
Akari.STAGE_TICK.submit(() -> {
|
||||
synchronized (((IMixinLockProvider) entitiesWorld).lock()) {
|
||||
tickEntities(entitiesWorld);
|
||||
entitiesWorld.getTracker().updatePlayers();
|
||||
entitiesWorld.explosionDensityCache.clear(); // Paper - Optimize explosions
|
||||
int worldSize = worlds.size();
|
||||
for (int i = 0; i < worldSize; i++) {
|
||||
// Impl Note:
|
||||
// Entities ticking: index 2 -> ... -> 0 -> 1 (parallel)
|
||||
// World ticking: index 1 -> ... (parallel) | 0 (main thread)
|
||||
int interlaceEntity = i + 2;
|
||||
WorldServer entityWorld = null;
|
||||
if (interlaceEntity < worldSize) {
|
||||
entityWorld = worlds.get(interlaceEntity);
|
||||
} else {
|
||||
if (tickedPrimaryEntities) {
|
||||
entityWorld = worlds.get(1);
|
||||
} else {
|
||||
entityWorld = worlds.get(0);
|
||||
tickedPrimaryEntities = true;
|
||||
}
|
||||
}
|
||||
entityWorld.timings.tickEntities.startTiming();
|
||||
WorldServer fEntityWorld = entityWorld;
|
||||
Akari.STAGE_ENTITY_TICK.submit(() -> {
|
||||
synchronized (((IMixinLockProvider) fEntityWorld).lock()) {
|
||||
tickEntities(fEntityWorld);
|
||||
fEntityWorld.getTracker().updatePlayers();
|
||||
fEntityWorld.explosionDensityCache.clear(); // Paper - Optimize explosions
|
||||
}
|
||||
}, null);
|
||||
|
||||
}, entityWorld);
|
||||
|
||||
int interlaceWorld = i + 1;
|
||||
if (interlaceWorld < worldSize) {
|
||||
WorldServer world = worlds.get(interlaceWorld);
|
||||
world.timings.doTick.startTiming();
|
||||
Akari.STAGE_WORLD_TICK.submit(() -> {
|
||||
synchronized (((IMixinLockProvider) world).lock()) {
|
||||
tickWorld(world);
|
||||
}
|
||||
}, world);
|
||||
}
|
||||
}
|
||||
|
||||
WorldServer primaryWorld = worlds.get(0);
|
||||
primaryWorld.timings.doTick.startTiming();
|
||||
synchronized (((IMixinLockProvider) primaryWorld).lock()) {
|
||||
tickWorld(primaryWorld);
|
||||
}
|
||||
primaryWorld.timings.doTick.stopTiming();
|
||||
|
||||
for (int i = 0; i < worldSize; i++) {
|
||||
WorldServer world = worlds.get(i);
|
||||
synchronized (((IMixinLockProvider) world).lock()) {
|
||||
tickWorld(world);
|
||||
long startTiming = System.nanoTime();
|
||||
if (i != 0) {
|
||||
((TimingHandler) Akari.STAGE_WORLD_TICK.take().get().timings.doTick).stopTiming(startTiming);
|
||||
startTiming = System.nanoTime();
|
||||
}
|
||||
((TimingHandler) Akari.STAGE_ENTITY_TICK.take().get().timings.tickEntities).stopTiming(startTiming);
|
||||
}
|
||||
|
||||
Akari.entityCallbackTiming.startTiming();
|
||||
for (int i = worlds.size(); i --> 0 ;) Akari.STAGE_TICK.take();
|
||||
Akari.entityCallbackTiming.stopTiming();
|
||||
|
||||
Akari.worldTiming.stopTiming();
|
||||
if (AkarinGlobalConfig.legacyWorldTimings) {
|
||||
for (int i = 0; i < worlds.size(); ++i) {
|
||||
WorldServer world = worlds.get(i);
|
||||
world.timings.tickEntities.stopTiming();
|
||||
world.timings.doTick.stopTiming();
|
||||
}
|
||||
}
|
||||
|
||||
Akari.callbackTiming.startTiming();
|
||||
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user