Deeper parallel world tick w/ Config and timings changes w/ Safe ensures #5

This commit is contained in:
Sotr
2018-06-07 18:40:10 +08:00
parent c38ade2ad8
commit 6d0edfa61b
6 changed files with 311 additions and 86 deletions

View File

@@ -0,0 +1,227 @@
/*
* This file is licensed under the MIT License (MIT).
*
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package co.aikar.timings;
import co.aikar.util.LoadingIntMap;
import io.akarin.api.Akari;
import io.akarin.server.core.AkarinGlobalConfig;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.bukkit.Bukkit;
import java.util.logging.Level;
class TimingHandler implements Timing {
private static int idPool = 1;
final int id = idPool++;
final String name;
private final boolean verbose;
private final Int2ObjectOpenHashMap<TimingData> children = new LoadingIntMap<>(TimingData::new);
final TimingData record;
private final TimingHandler groupHandler;
private volatile long start = 0; // Akarin - volatile
private volatile int timingDepth = 0; // Akarin - volatile
private boolean added;
private boolean timed;
private boolean enabled;
private TimingHandler parent;
TimingHandler(TimingIdentifier id) {
if (id.name.startsWith("##")) {
verbose = true;
this.name = id.name.substring(3);
} else {
this.name = id.name;
verbose = false;
}
this.record = new TimingData(this.id);
this.groupHandler = id.groupHandler;
TimingIdentifier.getGroup(id.group).handlers.add(this);
checkEnabled();
}
final void checkEnabled() {
enabled = Timings.timingsEnabled && (!verbose || Timings.verboseEnabled);
}
void processTick(boolean violated) {
if (timingDepth != 0 || record.getCurTickCount() == 0) {
timingDepth = 0;
start = 0;
return;
}
record.processTick(violated);
for (TimingData handler : children.values()) {
handler.processTick(violated);
}
}
@Override
public Timing startTimingIfSync() {
if (Bukkit.isPrimaryThread()) {
startTiming();
}
return this;
}
@Override
public void stopTimingIfSync() {
if (Bukkit.isPrimaryThread()) {
stopTiming(true); // Akarin - avoid twice thread check
}
}
@Override
public Timing startTiming() {
if (enabled && ++timingDepth == 1) {
start = System.nanoTime();
parent = TimingsManager.CURRENT;
TimingsManager.CURRENT = this;
}
return this;
}
@Override
public void stopTiming() {
// Akarin start - avoid twice thread check
stopTiming(false);
}
public void stopTiming(boolean sync) {
if (enabled && --timingDepth == 0 && start != 0) {
// Akarin start - silent async timing
if (Akari.silentTiming) { // It must be off-main thread now
start = 0;
return;
} else {
if (!sync && !Bukkit.isPrimaryThread()) {
if (AkarinGlobalConfig.silentAsyncTimings) {
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
new Throwable().printStackTrace();
}
start = 0;
return;
}
}
// Akarin end
addDiff(System.nanoTime() - start);
start = 0;
}
}
@Override
public void abort() {
if (enabled && timingDepth > 0) {
start = 0;
}
}
void addDiff(long diff) {
if (TimingsManager.CURRENT == this) {
TimingsManager.CURRENT = parent;
if (parent != null) {
parent.children.get(id).add(diff);
}
}
record.add(diff);
if (!added) {
added = true;
timed = true;
TimingsManager.HANDLERS.add(this);
}
if (groupHandler != null) {
groupHandler.addDiff(diff);
groupHandler.children.get(id).add(diff);
}
}
/**
* Reset this timer, setting all values to zero.
*
* @param full
*/
void reset(boolean full) {
record.reset();
if (full) {
timed = false;
}
start = 0;
timingDepth = 0;
added = false;
children.clear();
checkEnabled();
}
@Override
public TimingHandler getTimingHandler() {
return this;
}
@Override
public boolean equals(Object o) {
return (this == o);
}
@Override
public int hashCode() {
return id;
}
/**
* This is simply for the Closeable interface so it can be used with
* try-with-resources ()
*/
@Override
public void close() {
stopTimingIfSync();
}
public boolean isSpecial() {
return this == TimingsManager.FULL_SERVER_TICK || this == TimingsManager.TIMINGS_TICK;
}
boolean isTimed() {
return timed;
}
public boolean isEnabled() {
return enabled;
}
TimingData[] cloneChildren() {
final TimingData[] clonedChildren = new TimingData[children.size()];
int i = 0;
for (TimingData child : children.values()) {
clonedChildren[i++] = child.clone();
}
return clonedChildren;
}
}

View File

@@ -61,18 +61,16 @@ public abstract class Akari {
/* /*
* Timings * Timings
*/ */
private static Timing callbackTiming; public static Timing worldTiming = getWorldTiming();
public static Timing callbackTiming() { private static Timing getWorldTiming() {
if (callbackTiming == null) { try {
try { Method ofSafe = Timings.class.getDeclaredMethod("ofSafe", String.class);
Method ofSafe = Timings.class.getDeclaredMethod("ofSafe", String.class); ofSafe.setAccessible(true);
ofSafe.setAccessible(true); return worldTiming = (Timing) ofSafe.invoke(null, "Akarin - World");
callbackTiming = (Timing) ofSafe.invoke(null, "Akarin - Callback"); } catch (Throwable t) {
} catch (Throwable t) { t.printStackTrace();
t.printStackTrace(); return null;
}
} }
return callbackTiming;
} }
} }

View File

@@ -145,12 +145,12 @@ public class AkarinGlobalConfig {
/*========================================================================*/ /*========================================================================*/
public static List<String> extraAddress; public static List<String> extraAddress;
private static void extraAddress() { private static void extraAddress() {
extraAddress = getList("network.extra-local-address", Lists.newArrayList()); extraAddress = getList("bootstrap.extra-local-address", Lists.newArrayList());
} }
public static boolean legacyVersioningCompat; public static boolean legacyVersioningCompat;
private static void legacyVersioningCompat() { private static void legacyVersioningCompat() {
legacyVersioningCompat = getBoolean("bonus.legacy-versioning-compat", false); legacyVersioningCompat = getBoolean("alternative.legacy-versioning-compat", false);
} }
public static int registryTerminationSeconds; public static int registryTerminationSeconds;
@@ -160,6 +160,16 @@ public class AkarinGlobalConfig {
public static int playersPerIOThread; public static int playersPerIOThread;
private static void playersPerIOThread() { private static void playersPerIOThread() {
playersPerIOThread = getInt("chunk.players-per-chunk-io-thread", 50); playersPerIOThread = getInt("core.players-per-chunk-io-thread", 50);
}
public static boolean silentAsyncTimings;
private static void silentAsyncTimings() {
silentAsyncTimings = getBoolean("core.silent-async-timing", false);
}
public static boolean legacyWorldTimings;
private static void legacyWorldTimings() {
legacyWorldTimings = getBoolean("alternative.legacy-world-timings-required", false);
} }
} }

View File

@@ -1,5 +1,6 @@
package io.akarin.server.mixin.core; package io.akarin.server.mixin.core;
import java.io.File;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.FutureTask; import java.util.concurrent.FutureTask;
@@ -11,9 +12,13 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import co.aikar.timings.MinecraftTimings; import co.aikar.timings.MinecraftTimings;
import io.akarin.api.Akari; import io.akarin.api.Akari;
import io.akarin.server.core.AkarinGlobalConfig;
import net.minecraft.server.CrashReport; import net.minecraft.server.CrashReport;
import net.minecraft.server.CustomFunctionData; import net.minecraft.server.CustomFunctionData;
import net.minecraft.server.EntityPlayer; import net.minecraft.server.EntityPlayer;
@@ -26,6 +31,7 @@ import net.minecraft.server.ReportedException;
import net.minecraft.server.ServerConnection; import net.minecraft.server.ServerConnection;
import net.minecraft.server.SystemUtils; import net.minecraft.server.SystemUtils;
import net.minecraft.server.TileEntityHopper; import net.minecraft.server.TileEntityHopper;
import net.minecraft.server.World;
import net.minecraft.server.WorldServer; import net.minecraft.server.WorldServer;
@Mixin(value = MinecraftServer.class, remap = false) @Mixin(value = MinecraftServer.class, remap = false)
@@ -44,6 +50,14 @@ public class MixinMinecraftServer {
@Overwrite @Overwrite
public void b(MojangStatisticsGenerator generator) {} public void b(MojangStatisticsGenerator generator) {}
@Inject(method = "run()V", at = @At("HEAD"))
private void prerun(CallbackInfo info) {
for (int i = 0; i < worlds.size(); ++i) {
WorldServer world = worlds.get(i);
TileEntityHopper.skipHopperEvents = world.paperConfig.disableHopperMoveEvents || InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0;
}
}
@Shadow public CraftServer server; @Shadow public CraftServer server;
@Shadow @Mutable protected Queue<FutureTask<?>> j; @Shadow @Mutable protected Queue<FutureTask<?>> j;
@Shadow public Queue<Runnable> processQueue; @Shadow public Queue<Runnable> processQueue;
@@ -71,6 +85,21 @@ public class MixinMinecraftServer {
} }
} }
private void tickWorld(WorldServer world) {
try {
world.doTick();
} catch (Throwable throwable) {
CrashReport crashreport;
try {
crashreport = CrashReport.a(throwable, "Exception ticking world");
} catch (Throwable t){
throw new RuntimeException("Error generating crash report", t);
}
world.a(crashreport);
throw new ReportedException(crashreport);
}
}
@Overwrite @Overwrite
public void D() throws InterruptedException { public void D() throws InterruptedException {
MinecraftTimings.bukkitSchedulerTimer.startTiming(); MinecraftTimings.bukkitSchedulerTimer.startTiming();
@@ -104,36 +133,40 @@ public class MixinMinecraftServer {
} }
MinecraftTimings.timeUpdateTimer.stopTiming(); MinecraftTimings.timeUpdateTimer.stopTiming();
for (int i = 0; i < worlds.size(); ++i) { Akari.worldTiming.startTiming();
WorldServer mainWorld = worlds.get(i); if (AkarinGlobalConfig.legacyWorldTimings) {
WorldServer entityWorld = worlds.get(i + 1 < worlds.size() ? i + 1 : 0); for (int i = 0; i < worlds.size(); ++i) {
TileEntityHopper.skipHopperEvents = entityWorld.paperConfig.disableHopperMoveEvents || InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; worlds.get(i).timings.tickEntities.startTiming();
worlds.get(i).timings.doTick.startTiming();
Akari.silentTiming = true;
Akari.STAGE_TICK.submit(() -> tickEntities(entityWorld), null);
try {
mainWorld.timings.doTick.startTiming();
mainWorld.doTick();
mainWorld.timings.doTick.stopTiming();
} catch (Throwable throwable) {
CrashReport crashreport;
try {
crashreport = CrashReport.a(throwable, "Exception ticking world");
} catch (Throwable t){
throw new RuntimeException("Error generating crash report", t);
}
mainWorld.a(crashreport);
throw new ReportedException(crashreport);
} }
}
entityWorld.timings.tickEntities.startTiming(); Akari.silentTiming = true; // Disable timings
Akari.STAGE_TICK.take(); Akari.STAGE_TICK.submit(() -> {
entityWorld.timings.tickEntities.stopTiming(); for (int i = 0; i < worlds.size(); ++i) {
WorldServer world = worlds.get(i);
entityWorld.getTracker().updatePlayers(); tickEntities(world);
Akari.silentTiming = false; }
mainWorld.explosionDensityCache.clear(); // Paper - Optimize explosions }, null);
for (int i = 0; i < worlds.size(); ++i) {
WorldServer world = worlds.get(i);
tickWorld(world);
}
Akari.STAGE_TICK.take();
Akari.silentTiming = false; // Enable timings
Akari.worldTiming.stopTiming();
if (AkarinGlobalConfig.legacyWorldTimings) {
for (int i = 0; i < worlds.size(); ++i) {
worlds.get(i).timings.tickEntities.stopTiming();
worlds.get(i).timings.doTick.startTiming();
}
}
for (int i = 0; i < worlds.size(); ++i) {
WorldServer world = worlds.get(i);
world.getTracker().updatePlayers();
world.explosionDensityCache.clear(); // Paper - Optimize explosions
} }
MinecraftTimings.connectionTimer.startTiming(); MinecraftTimings.connectionTimer.startTiming();

View File

@@ -1,42 +0,0 @@
package io.akarin.server.mixin.core;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import io.akarin.api.Akari;
@Mixin(targets = "co.aikar.timings.TimingHandler", remap = false)
public class MixinTimingHandler {
@Shadow @Final String name;
@Shadow private long start = 0;
@Shadow private int timingDepth = 0;
@Shadow private boolean enabled;
@Shadow void addDiff(long diff) {}
@Overwrite
public void stopTiming() {
if (enabled && --timingDepth == 0 && start != 0) {
// Thread.currentThread() is an expensive operation, trying to avoid it
if (Akari.silentTiming) { // It must be off-main thread now
start = 0;
return;
} else {
if (!Bukkit.isPrimaryThread()) {
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
new Throwable().printStackTrace();
start = 0;
return;
}
}
addDiff(System.nanoTime() - start);
start = 0;
}
}
}

View File

@@ -16,7 +16,6 @@
"MixinMCUtil", "MixinMCUtil",
"MixinMetrics", "MixinMetrics",
"MixinCraftServer", "MixinCraftServer",
"MixinTimingHandler",
"MixinVersionCommand", "MixinVersionCommand",
"MixinMinecraftServer", "MixinMinecraftServer",
"MixinChunkIOExecutor", "MixinChunkIOExecutor",