Fully parallel world ticking

This commit is contained in:
Sotr
2018-08-09 15:16:55 +08:00
parent 3b4926bed2
commit 4fdc9f0166
6 changed files with 93 additions and 1773 deletions

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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");
}

View File

@@ -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